<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Openlayers 研讨会 | lovewhoilove 的小站</title><meta name="author" content="lovewhoilove"><meta name="copyright" content="lovewhoilove"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="引言本小节原文链接及内容原文链接：https:&#x2F;&#x2F;openlayers.org&#x2F;workshop&#x2F;en&#x2F;   欢迎来到Openlayers研讨会，Openlayers作为一个Web地图解决方案，本研讨会将带你全面了解Openlayers。 开发环境配置首先，除了需要下载最新的workshop release(压缩包名为openLayer-works-en.zip)压缩包(在Github上已归档)，">
<meta property="og:type" content="website">
<meta property="og:title" content="Openlayers 研讨会">
<meta property="og:url" content="https://lovewhoilove.gitee.io/workshop/">
<meta property="og:site_name" content="lovewhoilove 的小站">
<meta property="og:description" content="引言本小节原文链接及内容原文链接：https:&#x2F;&#x2F;openlayers.org&#x2F;workshop&#x2F;en&#x2F;   欢迎来到Openlayers研讨会，Openlayers作为一个Web地图解决方案，本研讨会将带你全面了解Openlayers。 开发环境配置首先，除了需要下载最新的workshop release(压缩包名为openLayer-works-en.zip)压缩包(在Github上已归档)，">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://lovewhoilove.gitee.io/img/avatar.png">
<meta property="article:published_time" content="2023-11-29T01:28:05.198Z">
<meta property="article:modified_time" content="2023-11-29T01:28:05.198Z">
<meta property="article:author" content="lovewhoilove">
<meta property="article:tag" content="地学网站">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://lovewhoilove.gitee.io/img/avatar.png"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://lovewhoilove.gitee.io/workshop/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="manifest" href="/img/pwa/manifest.json"/><meta name="msapplication-TileColor" content="#49b1f5"/><link rel="apple-touch-icon" sizes="180x180" href="/img/pwa/apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="/img/pwa/32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/img/pwa/16.png"/><link rel="mask-icon" href="/img/pwa/32.png" color="#5bbad5"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":false},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: {"limitCount":50,"languages":{"author":"作者: lovewhoilove","link":"链接: ","source":"来源: lovewhoilove 的小站","info":"著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。"}},
  lightbox: 'fancybox',
  Snackbar: undefined,
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: true,
  islazyload: true,
  isAnchor: false,
  percent: {
    toc: false,
    rightside: true,
  }
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Openlayers 研讨会',
  isPost: false,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-11-29 09:28:05'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/css/progress_bar.css"><link rel="stylesheet" href="/css/custom.css"><svg aria-hidden="true" style="position:absolute; overflow:hidden; width:0; height:0"><symbol id="icon-sun" viewBox="0 0 1024 1024"><path d="M960 512l-128 128v192h-192l-128 128-128-128H192v-192l-128-128 128-128V192h192l128-128 128 128h192v192z" fill="#FFD878" p-id="8420"></path><path d="M736 512a224 224 0 1 0-448 0 224 224 0 1 0 448 0z" fill="#FFE4A9" p-id="8421"></path><path d="M512 109.248L626.752 224H800v173.248L914.752 512 800 626.752V800h-173.248L512 914.752 397.248 800H224v-173.248L109.248 512 224 397.248V224h173.248L512 109.248M512 64l-128 128H192v192l-128 128 128 128v192h192l128 128 128-128h192v-192l128-128-128-128V192h-192l-128-128z" fill="#4D5152" p-id="8422"></path><path d="M512 320c105.888 0 192 86.112 192 192s-86.112 192-192 192-192-86.112-192-192 86.112-192 192-192m0-32a224 224 0 1 0 0 448 224 224 0 0 0 0-448z" fill="#4D5152" p-id="8423"></path></symbol><symbol id="icon-moon" viewBox="0 0 1024 1024"><path d="M611.370667 167.082667a445.013333 445.013333 0 0 1-38.4 161.834666 477.824 477.824 0 0 1-244.736 244.394667 445.141333 445.141333 0 0 1-161.109334 38.058667 85.077333 85.077333 0 0 0-65.066666 135.722666A462.08 462.08 0 1 0 747.093333 102.058667a85.077333 85.077333 0 0 0-135.722666 65.024z" fill="#FFB531" p-id="11345"></path><path d="M329.728 274.133333l35.157333-35.157333a21.333333 21.333333 0 1 0-30.165333-30.165333l-35.157333 35.157333-35.114667-35.157333a21.333333 21.333333 0 0 0-30.165333 30.165333l35.114666 35.157333-35.114666 35.157334a21.333333 21.333333 0 1 0 30.165333 30.165333l35.114667-35.157333 35.157333 35.157333a21.333333 21.333333 0 1 0 30.165333-30.165333z" fill="#030835" p-id="11346"></path></symbol></svg><!-- hexo injector head_end start --><link rel="stylesheet" href="https://npm.elemecdn.com/hexo-butterfly-wowjs/lib/animate.min.css" media="print" onload="this.media='screen'"><!-- hexo injector head_end end --><meta name="generator" content="Hexo 5.4.2"></head><body><div id="loading-box" onclick="document.getElementById(&quot;loading-box&quot;).classList.add(&quot;loaded&quot;)"><div class="loading-bg"><div class="loading-img"></div><div class="loading-image-dot"></div></div></div><script>const preloader = {
  endLoading: () => {
    document.body.style.overflow = 'auto';
    document.getElementById('loading-box').classList.add("loaded")
  },
  initLoading: () => {
    document.body.style.overflow = '';
    document.getElementById('loading-box').classList.remove("loaded")

  }
}
window.addEventListener('load',()=> { preloader.endLoading() })

if (true) {
  document.addEventListener('pjax:send', () => { preloader.initLoading() })
  document.addEventListener('pjax:complete', () => { preloader.endLoading() })
}</script><link rel="stylesheet" href="/css/custom.css"/><script src="https://cdn.jsdelivr.net/npm/pace-js/pace.min.js"></script><div id="web_bg"></div><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src= "/img/friend_404.gif" data-lazy-src="/img/avatar.png" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">250</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">327</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">2</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-newspaper"></i><span> 文章相关</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/archives/"><i class="fa-fw fas fa-timeline"></i><span> 时间轴</span></a></li><li><a class="site-page child" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></li></ul></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-layer-group"></i><span> Openlayers</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/doc/"><i class="fa-fw fas fa-book-reader"></i><span> 说明文档</span></a></li><li><a class="site-page child" href="/quick-start/"><i class="fa-fw fas fa-bolt"></i><span> 快速入门</span></a></li><li><a class="site-page child" href="/tutorials/"><i class="fa-fw fas fa-book"></i><span> 教程</span></a></li><li><a class="site-page child" href="/faq/"><i class="fa-fw fas fa-question"></i><span> 常见问题</span></a></li><li><a class="site-page child" href="/workshop/"><i class="fa-fw fas fa-graduation-cap"></i><span> 研讨会</span></a></li><li><a class="site-page child" href="/examples/"><i class="fa-fw fas fa-laptop-code"></i><span> 示例</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 网址导航</span></a></div></div></div></div><div class="page" id="body-wrap"><header class="not-home-page" id="page-header" style="background-image: url('/img/homepage-banner.webp')"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">lovewhoilove 的小站</a></span><div id="menus"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i><span> 搜索</span></a></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-newspaper"></i><span> 文章相关</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/archives/"><i class="fa-fw fas fa-timeline"></i><span> 时间轴</span></a></li><li><a class="site-page child" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></li><li><a class="site-page child" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></li></ul></div><div class="menus_item"><a class="site-page group" href="javascript:void(0);"><i class="fa-fw fas fa-layer-group"></i><span> Openlayers</span><i class="fas fa-chevron-down"></i></a><ul class="menus_item_child"><li><a class="site-page child" href="/doc/"><i class="fa-fw fas fa-book-reader"></i><span> 说明文档</span></a></li><li><a class="site-page child" href="/quick-start/"><i class="fa-fw fas fa-bolt"></i><span> 快速入门</span></a></li><li><a class="site-page child" href="/tutorials/"><i class="fa-fw fas fa-book"></i><span> 教程</span></a></li><li><a class="site-page child" href="/faq/"><i class="fa-fw fas fa-question"></i><span> 常见问题</span></a></li><li><a class="site-page child" href="/workshop/"><i class="fa-fw fas fa-graduation-cap"></i><span> 研讨会</span></a></li><li><a class="site-page child" href="/examples/"><i class="fa-fw fas fa-laptop-code"></i><span> 示例</span></a></li></ul></div><div class="menus_item"><a class="site-page" href="/link/"><i class="fa-fw fas fa-link"></i><span> 网址导航</span></a></div></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav><div id="page-site-info"><h1 id="site-title">Openlayers 研讨会</h1></div></header><main class="layout" id="content-inner"><div id="page"><div id="article-container"><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/">https://openlayers.org/workshop/en/</a></p>
<iframe src="https://openlayers.org/workshop/en/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>欢迎来到Openlayers研讨会，Openlayers作为一个Web地图解决方案，本研讨会将带你全面了解Openlayers。</p>
<h3 id="开发环境配置"><a href="#开发环境配置" class="headerlink" title="开发环境配置"></a>开发环境配置</h3><p>首先，除了需要下载最新的<a target="_blank" rel="noopener" href="https://github.com/openlayers/workshop/releases">workshop release</a>(压缩包名为<code>openLayer-works-en.zip</code>)压缩包(在Github上已归档)，还需要安装<a target="_blank" rel="noopener" href="https://nodejs.org/zh-cn">Node</a>(<code>v16</code>或更高版本)，使得项目代码可以正常运行。<br>解压压缩包后，进入<code>openlayers-workshop-en</code>目录，输入如下命令安装<code>package.json</code>文件中配置的开发依赖项:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install</span><br></pre></td></tr></table></figure>
<p>现在，运行下面的命令即可启动项目代码，而解压的压缩包除了项目代码，还提供了研讨会的文档资料。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm start</span><br></pre></td></tr></table></figure>
<p>运行上述命令后，将启动一个开发服务器，你可以在其中阅读研讨会文档并完成练习。在浏览器输入启动地址<code>http://localhost:5173/</code>即可在浏览器打开项目(或者按住键盘的<code>Ctrl</code>键然后点击启动地址也可打开浏览器)，如下图，会弹出一个确认窗口，表示已正常运行项目。</p>
<img src= "/img/friend_404.gif" data-lazy-src="https://u-1303826255.cos.ap-nanjing.myqcloud.com/openlayers/Snipaste_2023-04-08_15-45-21.png?imageMogr2/quality/75/format/webp/interlace/0" style="zoom:50%;" />

<p>你还可以在<code>http://localhost:5173/doc/</code>上阅读研讨会文档。</p>
<img src= "/img/friend_404.gif" data-lazy-src="https://u-1303826255.cos.ap-nanjing.myqcloud.com/openlayers/Snipaste_2023-04-08_15-46-36.png?imageMogr2/quality/75/format/webp/interlace/0" style="zoom:50%;" />

<h3 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h3><p>本研讨会以一系列的章节形式呈现，在每个章节中，你将实现该章节特定的目标任务，每个章节都建立在之前章节的基础之上，旨在迭代地建立你的知识库。<br>本研讨会将介绍以下章节的内容：</p>
<ul>
<li><strong>基础概念</strong>：了解如何在网页中添加地图。</li>
<li><strong>矢量数据</strong>：处理矢量数据。</li>
<li><strong>移动地图和传感器</strong>：带有GPS和指南针的(移动)手机地图。</li>
<li><strong>GeoTIFF渲染</strong>：生成并可视化来自从GeoTIFF数据源的数据切片。</li>
<li><strong>矢量切片和Mapbox样式</strong>：使用矢量切片创建一幅漂亮的地图。</li>
<li><strong>WebGL点的渲染</strong>：使用WebGL来渲染点数据。</li>
<li><strong>部署</strong>：构建生产环境下的应用程序。</li>
</ul>
<h2 id="基础概念"><a href="#基础概念" class="headerlink" title="基础概念"></a>基础概念</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/basics/">https://openlayers.org/workshop/en/basics/</a></p>
<iframe src="https://openlayers.org/workshop/en/basics/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>确保你已经完成了上一小节<a href="#%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE">开发环境的配置</a>，并使开发服务器运行起来。<br>接下来，让我们开始使用OpenLayers创建一个简单的Web地图页面，并理解代码。<br>在OpenLayers中，地图是渲染到网页上的图层的集合，要创建地图，你需要一些用于创建地图视图的标记元素(如<code>&lt;div&gt;</code>元素)、一些使地图视图在页面上具有适当的尺寸的样式，以及初始化地图Javascript代码。<br>OpenLayers支持不同类型的图层：</p>
<ul>
<li><strong>切片图层</strong>，平铺的栅格数据切片集合(原文：Tile layers for tiled raster tile sets)</li>
<li><strong>影像图层</strong>，影像文件或根据地图范围需要提供的影像(原文：Image layers for static images or images that are provided on demand for the map’s extent)</li>
<li><strong>矢量图层</strong>，矢量数据文件或地图当前范围的矢量数据(原文：Vector layers for vector data from static files or for the map’s current extent)</li>
<li><strong>矢量切片图层</strong>，平铺的矢量切片集合(原文：Vector tile layers for tiled vector tile sets)</li>
</ul>
<p>除了图层和视图之外，地图还可以配置一些控件(<code>contorl</code>，即地图上方的UI元素)和交互(<code>interaction</code>，即对地图上的触摸或(鼠标)指针手势做出反应的组件)。</p>
<h3 id="界面的编写"><a href="#界面的编写" class="headerlink" title="界面的编写"></a>界面的编写</h3><p>编辑项目的根目录下的<code>index.html</code>文件，内容如下：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>OpenLayers<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="keyword">@import</span> <span class="string">&quot;node_modules/ol/ol.css&quot;</span>;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">html</span>, <span class="selector-tag">body</span>, <span class="selector-id">#map-container</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: sans-serif;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;map-container&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./main.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>上述HTML页面包含一个<code>&lt;div&gt;</code>，其<code>id</code>为<code>map-container</code>，用作地图的目标容器。<code>&lt;style&gt;</code>样式会使地图容器充满整个页面。</p>
<h3 id="程序的入口文件"><a href="#程序的入口文件" class="headerlink" title="程序的入口文件"></a>程序的入口文件</h3><p>为了使用OpenLayers，我们安装了来自npm的<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/ol">ol包</a>，这在上一小节的<code>npm install</code>步骤中已经完成。如果你是从头开始开发一个新的应用程序，你应该在终端中运行<code>npm install ol</code>。</p>
<p>作为应用程序的入口位置，我们创建了一个<code>main.js</code>文件，并将其保存在项目代码的根目录下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="variable constant_">OSM</span> <span class="keyword">from</span> <span class="string">&#x27;ol/source/OSM&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">TileLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/Tile&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Map</span>, <span class="title class_">View</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;ol&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;fromLonLat&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/proj&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">layers</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">      <span class="attr">source</span>: <span class="keyword">new</span> <span class="title function_">OSM</span>(),</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">view</span>: <span class="keyword">new</span> <span class="title class_">View</span>(&#123;</span><br><span class="line">    <span class="attr">center</span>: <span class="title function_">fromLonLat</span>([<span class="number">0</span>, <span class="number">0</span>]),</span><br><span class="line">    <span class="attr">zoom</span>: <span class="number">2</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>上述代码块顶部的导入行，是从<code>ol</code>包中引入所需的模块，将需要的包导入后，我们首先要创建一个Map对象，配置其<code>target</code>为上述<code>&lt;div&gt;</code>容器的<code>id</code>属性；然后需要为地图配置一个切片图层对象(<code>TileLayer</code>)和一个<code>XYZSource</code>数据源对象；最后，需要为<code>View</code>对象配置初始的<code>zoom</code>值，以及视图投影下地图视图的<code>center</code>属性；为了提供地理坐标，我们使用了<code>ol/proj</code>模块中的<code>fromLonLat</code>函数进行了坐标转换。</p>
<h3 id="查看地图"><a href="#查看地图" class="headerlink" title="查看地图"></a>查看地图</h3><p>现在运行项目代码，让我们在web浏览器中打开地图：<code>http://localhost:5173/</code>，它应该是这样的:<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/basics/map.png" alt="一张世界地图"></p>
<h3 id="扩展阅读"><a href="#扩展阅读" class="headerlink" title="扩展阅读"></a>扩展阅读</h3><p>在<a href="#%E9%83%A8%E7%BD%B2">研讨会的最后一章</a>中，我们将学习如何创建用于部署的应用程序的生产版本。<br>以此为起点，在学习的过程中，我们建议查阅<a href="/examples/">示例</a>。另外，官方的<a href="/api/">API文档</a>也提供了OpenLayers的所有类和函数的参考。</p>
<h2 id="矢量数据"><a href="#矢量数据" class="headerlink" title="矢量数据"></a>矢量数据</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vector/">https://openlayers.org/workshop/en/vector/</a></p>
<iframe src="https://openlayers.org/workshop/en/vector/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>在本章节中，我们将创建一个用于处理矢量数据的基本编辑器。目标是让用户可以导入数据、绘制新要素、修改现有要素并导出结果。除此之外，还会处理GeoJSON数据，但如果你有兴趣处理其他的数据源，OpenLayers也是支持的，并且它支持多种格式矢量数据。</p>
<h3 id="渲染GeoJSON"><a href="#渲染GeoJSON" class="headerlink" title="渲染GeoJSON"></a>渲染GeoJSON</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vector/geojson.html">https://openlayers.org/workshop/en/vector/geojson.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vector/geojson.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>在开始编辑之前，我们先来看一下使用矢量数据源和图层对基本要素的渲染。在项目的<code>data</code>目录中包括了一个名为<code>countries.json</code>的GeoJSON文件。我们会先加载数据，然后在地图上进行渲染。<br>首先，编辑<code>index.html</code>，为地图添加一个深色背景:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>OpenLayers<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="keyword">@import</span> <span class="string">&quot;node_modules/ol/ol.css&quot;</span>;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">html</span>, <span class="selector-tag">body</span>, <span class="selector-id">#map-container</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: sans-serif;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">background-color</span>: <span class="number">#04041b</span>;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;map-container&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./main.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>我们将引入处理矢量数据三个关键要素:</p>
<ul>
<li>用于读写序列化数据的format对象(本例中为GeoJSON)，<strong>注</strong>：这里的format容易翻译为格式，其实format是矢量数据源对象的一个配置选项，详见<a target="_blank" rel="noopener" href="https://openlayers.org/en/latest/apidoc/module-ol_source_Vector-VectorSource.html">api文档</a>。</li>
<li>用于获取数据和管理要素空间索引的<strong>矢量数据源对象</strong>。</li>
<li>用于在地图上渲染要素的<strong>矢量图层</strong>对象。</li>
</ul>
<p>更新<code>main.js</code>代码，以便加载并渲染包含GeoJSON要素的本地文件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">GeoJSON</span> <span class="keyword">from</span> <span class="string">&#x27;ol/format/GeoJSON&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Map</span> <span class="keyword">from</span> <span class="string">&#x27;ol/Map&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VectorLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/Vector&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VectorSource</span> <span class="keyword">from</span> <span class="string">&#x27;ol/source/Vector&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">View</span> <span class="keyword">from</span> <span class="string">&#x27;ol/View&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">layers</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">VectorLayer</span>(&#123;</span><br><span class="line">      <span class="attr">source</span>: <span class="keyword">new</span> <span class="title class_">VectorSource</span>(&#123;</span><br><span class="line">        <span class="attr">format</span>: <span class="keyword">new</span> <span class="title class_">GeoJSON</span>(),</span><br><span class="line">        <span class="attr">url</span>: <span class="string">&#x27;./data/countries.json&#x27;</span>,</span><br><span class="line">      &#125;),</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">view</span>: <span class="keyword">new</span> <span class="title class_">View</span>(&#123;</span><br><span class="line">    <span class="attr">center</span>: [<span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">    <span class="attr">zoom</span>: <span class="number">2</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>现在，你可以打开这个<code>http://localhost:5173/</code>链接中看到带有国家边界的地图。<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vector/geojson.png" alt="GeoJSON 要素"></p>
<p>假设我们将多次重新加载页面，如果地图留在我们重新加载时的位置就更好了，我们可以引入<code>Link</code>交互来实现这一点。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Link</span> <span class="keyword">from</span> <span class="string">&#x27;ol/interaction/Link&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>接下来，我们需要给地图对象分配给一个变量(名为<code>map</code>)，方便向其添加交互：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br></pre></td></tr></table></figure>
<p>现在，我们可以向我们的地图添加一个<code>Link</code>交互：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">map.<span class="title function_">addInteraction</span>(<span class="keyword">new</span> <span class="title class_">Link</span>());</span><br></pre></td></tr></table></figure>
<p>现在你应该看到，页面重新加载时地图视图得以保持，并且返回按钮的工作原理和你期望的一样。</p>
<p>文档在这里没有讲解清楚，为此，我特意测试了一下这个功能并录制了一个效果图：多次拖动地图后，刷新页面，地图仍然保持在了刷新前的位置，认真的小伙伴可能注意到地址栏其实记录了每次移动后坐标、缩放级别、旋转角度等参数，使得我们在多次拖动地图后，单击浏览器的返回键及前进键便可以切换至上一视图和下一视图。<br><img src= "/img/friend_404.gif" data-lazy-src="https://u-1303826255.cos.ap-nanjing.myqcloud.com/openlayers/202304081636248.gif?imageMogr2/quality/75/format/webp/interlace/0" style="zoom: 67%;" /></p>
<h3 id="拖放交互"><a href="#拖放交互" class="headerlink" title="拖放交互"></a>拖放交互</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vector/drag-n-drop.html">https://openlayers.org/workshop/en/vector/drag-n-drop.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vector/drag-n-drop.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>对于要创建的要素编辑器，我们希望用户能够导入自己的数据进行编辑。为此，我们将使用<code>DragAndDrop</code>交互。与前面一样，我们将继续使用GeoJSON format对象来解析要素，但是可以将交互配置为使用任意数量的要素格式(即formatConstructors属性值是一个数组，我们可以为其添加解析其他格式数据的format对象)。</p>
<p>首先创建一个Map对象，并将其分配给一个名为map的变量：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br></pre></td></tr></table></figure>

<p>然后在<code>main.js</code>中引入<code>DragAndDrop</code>包：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">DragAndDrop</span> <span class="keyword">from</span> <span class="string">&#x27;ol/interaction/DragAndDrop&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>接下来，我们将创建一个没有初始化数据的矢量数据源，这与上一个示例中从远程位置加载数据不同，此数据源将存储用户拖放到地图上的要素。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> source = <span class="keyword">new</span> <span class="title class_">VectorSource</span>();</span><br></pre></td></tr></table></figure>
<p>现在，我们用的空的矢量数据源创建一个图层，并将图层添加到地图中。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">VectorLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">&#125;);</span><br><span class="line">map.<span class="title function_">addLayer</span>(layer);</span><br></pre></td></tr></table></figure>
<p>最后，我们将创建一个拖放交互，配置它与我们的矢量数据源一起工作，并将它添加到地图:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">map.<span class="title function_">addInteraction</span>(</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">DragAndDrop</span>(&#123;</span><br><span class="line">    <span class="attr">source</span>: source,</span><br><span class="line">    <span class="attr">formatConstructors</span>: [<span class="title class_">GeoJSON</span>],</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>现在，你应该能够将GeoJSON文件拖放到地图上，并看到其中的数据被渲染。<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vector/drag-n-drop.png" alt="拖放文件到地图上"></p>
<h3 id="修改要素"><a href="#修改要素" class="headerlink" title="修改要素"></a>修改要素</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vector/modify.html">https://openlayers.org/workshop/en/vector/modify.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vector/modify.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>现在，用户能够将数据加载到编辑器中，但为了能够编辑要素，我们需要使用<code>Modify</code>交互，接下来将对其进行配置，以修改矢量数据源上的要素。<br>首先，在<code>main.js</code>中引入<code>Modify</code>交互：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Modify</span> <span class="keyword">from</span> <span class="string">&#x27;ol/interaction/Modify&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>然后，需要创建一个连接到矢量数据源的新交互，并将其添加到map(在<code>main.js</code>文件的底部):</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">map.<span class="title function_">addInteraction</span>(</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Modify</span>(&#123;</span><br><span class="line">    <span class="attr">source</span>: source,</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>将数据添加到地图后，请确认你可以通过拖动要素的顶点来修改要素，也可以通过按住键盘上的<code>Alt</code>键并单击来删除顶点。<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vector/modify.png" alt="修改要素"></p>
<h3 id="绘制新要素"><a href="#绘制新要素" class="headerlink" title="绘制新要素"></a>绘制新要素</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vector/draw.html">https://openlayers.org/workshop/en/vector/draw.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vector/draw.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>我们的要素编辑器现在可以用于加载数据和修改要素，接下来，我们将添加一个<code>Draw</code>交互，允许用户绘制新的要素，并将其添加到数据源中。<br>首先，在<code>main.js</code>中引入<code>Draw</code>交互：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Draw</span> <span class="keyword">from</span> <span class="string">&#x27;ol/interaction/Draw&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>现在，创建一个绘制交互，并配置为绘制多边形，并将它们添加到矢量数据源中:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">map.<span class="title function_">addInteraction</span>(</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Draw</span>(&#123;</span><br><span class="line">    <span class="attr">type</span>: <span class="string">&#x27;Polygon&#x27;</span>,</span><br><span class="line">    <span class="attr">source</span>: source,</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>绘制交互的<code>type</code>属性控制绘制几何图形的类型，该值可以是任何GeoJSON的几何类型。</p>
<p>有了绘制交互组件，我们现在就可以向矢量数据源添加新的要素了。<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vector/draw.png" alt="加勒比海的一个新岛国"></p>
<h3 id="开启捕捉交互"><a href="#开启捕捉交互" class="headerlink" title="开启捕捉交互"></a>开启捕捉交互</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vector/snap.html">https://openlayers.org/workshop/en/vector/snap.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vector/snap.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>你可能已经注意到，很容易绘制出与现有要素不能很好对齐的要素，此外，当修改要素时，我们可能会破坏拓扑关系-在之前相邻的多边形之间添加一个空隙，<code>Snap</code>交互可用于在绘制和编辑要素时保留拓扑关系。<br>首先，在<code>main.js</code>中引入<code>Snap</code>交互：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Snap</span> <span class="keyword">from</span> <span class="string">&#x27;ol/interaction/Snap&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>与其他编辑交互组件一样，我们将配置捕捉交互组件，并使用矢量数据源，并将交互对象添加至地图：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">map.<span class="title function_">addInteraction</span>(</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Snap</span>(&#123;</span><br><span class="line">    <span class="attr">source</span>: source,</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>在绘制、修改和捕捉交互都处于活动状态的情况下，我们可以在保持拓扑关系的同时编辑数据。<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vector/snap.png" alt="使用捕捉交互合并各个国家"></p>
<h3 id="下载要素"><a href="#下载要素" class="headerlink" title="下载要素"></a>下载要素</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vector/download.html">https://openlayers.org/workshop/en/vector/download.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vector/download.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>上传数据并进行编辑后，我们希望能够将编辑后的数据下载下来。为此，我们需要将要素数据序列化为GeoJSON，并创建一个<code>&lt;a&gt;</code>元素，该元素具有触发浏览器的文件保存对话框的<code>download</code>属性。与此同时，我们将在地图中添加一个按钮，让用户清除现有要素并重新开始。<br>首先，在<code>index.html</code>中的<code>map</code>容器之后添加以下HTML元素：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;tools&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">a</span> <span class="attr">id</span>=<span class="string">&quot;clear&quot;</span>&gt;</span>Clear<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">a</span> <span class="attr">id</span>=<span class="string">&quot;download&quot;</span> <span class="attr">download</span>=<span class="string">&quot;features.json&quot;</span>&gt;</span>Download<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>将以下内容添加到<code>index.html</code>中的<code>&lt;style&gt;</code>标签中：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#tools</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">1rem</span>;</span><br><span class="line">  <span class="attribute">right</span>: <span class="number">1rem</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-id">#tools</span> <span class="selector-tag">a</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: inline-block;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">0.5rem</span>;</span><br><span class="line">  <span class="attribute">background</span>: white;</span><br><span class="line">  <span class="attribute">cursor</span>: pointer;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>清除要素则比较简单，矢量数据源对象有一个<code>soure.clear()</code>方法，我们希望单击“清除”按钮来调用该方法，因此我们将在<code>main.js</code>中为清除按钮添加单击的侦听器：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> clear = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;clear&#x27;</span>);</span><br><span class="line">clear.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  source.<span class="title function_">clear</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>我们将使用GeoJSON格式对象来序列化要下载的数据，因为我们希望“下载”按钮在编辑过程中的任何时候都能正常运行，所以我们将在数据源对象的<code>change</code>事件中序列化要素，并为锚元素的<code>href</code>属性构造一个数据URI：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> format = <span class="keyword">new</span> <span class="title class_">GeoJSON</span>(&#123;<span class="attr">featureProjection</span>: <span class="string">&#x27;EPSG:3857&#x27;</span>&#125;);</span><br><span class="line"><span class="keyword">const</span> download = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;download&#x27;</span>);</span><br><span class="line">source.<span class="title function_">on</span>(<span class="string">&#x27;change&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> features = source.<span class="title function_">getFeatures</span>();</span><br><span class="line">  <span class="keyword">const</span> json = format.<span class="title function_">writeFeatures</span>(features);</span><br><span class="line">  download.<span class="property">href</span> =</span><br><span class="line">    <span class="string">&#x27;data:application/json;charset=utf-8,&#x27;</span> + <span class="built_in">encodeURIComponent</span>(json);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vector/download.png" alt="清除和下载数据的按钮"></p>
<h3 id="让加载的数据看起来更漂亮"><a href="#让加载的数据看起来更漂亮" class="headerlink" title="让加载的数据看起来更漂亮"></a>让加载的数据看起来更漂亮</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vector/style.html">https://openlayers.org/workshop/en/vector/style.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vector/style.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>此时，我们有了一个具有基本导入、编辑和导出功能的要素编辑器。但是我们没有花费任何时间试图使要素看起来更好看。当你在OpenLayers中创建矢量图层时，你将得到一组默认样式，编辑交互(包括绘制和修改交互)也有自己的默认样式。在编辑过程中，几何图形的笔划比较粗，解决办法是给矢量图层设置<code>style</code>属性并编辑交互。</p>
<p>首先，我们导入要用到的类：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Style</span>, <span class="title class_">Fill</span>, <span class="title class_">Stroke</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/style&#x27;</span>;</span><br></pre></td></tr></table></figure>
<h4 id="静态样式"><a href="#静态样式" class="headerlink" title="静态样式"></a>静态样式</h4><p>如果我们想给所有要素赋予相同的样式，可以这样配置我们的矢量图层：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">VectorLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">  <span class="attr">style</span>: <span class="keyword">new</span> <span class="title class_">Style</span>(&#123;</span><br><span class="line">    <span class="attr">fill</span>: <span class="keyword">new</span> <span class="title class_">Fill</span>(&#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="string">&#x27;red&#x27;</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="attr">stroke</span>: <span class="keyword">new</span> <span class="title class_">Stroke</span>(&#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="string">&#x27;white&#x27;</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>也可以将<code>style</code>属性设置为一组样式。例如，这允许渲染套接线(下面是宽笔划，上面是更窄的笔划)【感觉这里缺少一块代码示例，但基本能看懂】。</p>
<h4 id="动态样式"><a href="#动态样式" class="headerlink" title="动态样式"></a>动态样式</h4><p>当你需要根据有关要素或当前视图分辨率的内容来决定如何渲染每个要素时，可以使用样式函数配置矢量图层。每个渲染帧上的每个要素都会调用该函数，因此，如果你有许多要素并希望保持良好的渲染性能，编写一个高效的函数是很重要的。<br>下面是一个使用两种样式之一呈现要素的示例，具体取决于“name”属性是以“A-M”开头还是以“N-Z”开头(完全是人为设计的示例)。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">VectorLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">  <span class="attr">style</span>: <span class="keyword">function</span> (<span class="params">feature, resolution</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> name = feature.<span class="title function_">get</span>(<span class="string">&#x27;name&#x27;</span>).<span class="title function_">toUpperCase</span>();</span><br><span class="line">    <span class="keyword">return</span> name &lt; <span class="string">&#x27;N&#x27;</span> ? style1 : style2; <span class="comment">// assuming these are created elsewhere</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h4 id="根据几何图形的面积来设置样式"><a href="#根据几何图形的面积来设置样式" class="headerlink" title="根据几何图形的面积来设置样式"></a>根据几何图形的面积来设置样式</h4><p>为了了解动态样式的工作原理，我们将创建一个样式函数，该函数将基于几何图形的面积来渲染要素。为此，我们将使用npm上的<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/colormap">colormap</a>包。我们可以将其添加到依赖项中，如下所示：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install colormap</span><br></pre></td></tr></table></figure>
<p>现在，我们需要导入<code>colormap</code>包和<code>ol/sphere</code>来进行球面面积的计算。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> colormap <span class="keyword">from</span> <span class="string">&#x27;colormap&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;getArea&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/sphere&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>接下来，我们将编写两个函数来根据几何图形的面积确定颜色:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> min = <span class="number">1e8</span>; <span class="comment">// 最小面积</span></span><br><span class="line"><span class="keyword">const</span> max = <span class="number">2e13</span>; <span class="comment">// 最大面积</span></span><br><span class="line"><span class="keyword">const</span> steps = <span class="number">50</span>;</span><br><span class="line"><span class="keyword">const</span> ramp = <span class="title function_">colormap</span>(&#123;</span><br><span class="line">  <span class="attr">colormap</span>: <span class="string">&#x27;blackbody&#x27;</span>,</span><br><span class="line">  <span class="attr">nshades</span>: steps,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">clamp</span>(<span class="params">value, low, high</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title class_">Math</span>.<span class="title function_">max</span>(low, <span class="title class_">Math</span>.<span class="title function_">min</span>(value, high));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getColor</span>(<span class="params">feature</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> area = <span class="title function_">getArea</span>(feature.<span class="title function_">getGeometry</span>());</span><br><span class="line">  <span class="keyword">const</span> f = <span class="title class_">Math</span>.<span class="title function_">pow</span>(<span class="title function_">clamp</span>((area - min) / (max - min), <span class="number">0</span>, <span class="number">1</span>), <span class="number">1</span> / <span class="number">2</span>);</span><br><span class="line">  <span class="keyword">const</span> index = <span class="title class_">Math</span>.<span class="title function_">round</span>(f * (steps - <span class="number">1</span>));</span><br><span class="line">  <span class="keyword">return</span> ramp[index];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>现在我们可以添加一个函数，该函数将基于几何图形的面积来创建具有填充颜色的样式，并将此函数设置为矢量图层的<code>style</code>属性：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">VectorLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">  <span class="attr">style</span>: <span class="keyword">function</span> (<span class="params">feature</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Style</span>(&#123;</span><br><span class="line">      <span class="attr">fill</span>: <span class="keyword">new</span> <span class="title class_">Fill</span>(&#123;</span><br><span class="line">        <span class="attr">color</span>: <span class="title function_">getColor</span>(feature),</span><br><span class="line">      &#125;),</span><br><span class="line">      <span class="attr">stroke</span>: <span class="keyword">new</span> <span class="title class_">Stroke</span>(&#123;</span><br><span class="line">        <span class="attr">color</span>: <span class="string">&#x27;rgba(255,255,255,0.8)&#x27;</span>,</span><br><span class="line">      &#125;),</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vector/style.png" alt="按面积着色的要素"></p>
<h2 id="移动地图和传感器"><a href="#移动地图和传感器" class="headerlink" title="移动地图和传感器"></a>移动地图和传感器</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/mobile/">https://openlayers.org/workshop/en/mobile/</a></p>
<iframe src="https://openlayers.org/workshop/en/mobile/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>在本节内容中，我们将创建一个移动地图，显示用户的GPS位置和航向。此示例的目的是展示如何将OpenLayers与浏览器API和第三方实用程序集成。<br>只需几行代码，我们就可以利用浏览器的地理定位API获取GPS位置，并使用<a target="_blank" rel="noopener" href="https://npmjs.com/package/kompas">kompas</a>程序从设备的陀螺仪获取航向。使用一个矢量图层，我们可以很容易地在地图上显示结果。</p>
<h3 id="移动地图"><a href="#移动地图" class="headerlink" title="移动地图"></a>移动地图</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/mobile/map.html">https://openlayers.org/workshop/en/mobile/map.html</a></p>
<iframe src="https://openlayers.org/workshop/en/mobile/map.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>OpenLayers支持开箱即用的移动设备，提供多点触摸手势，如多点触摸缩放和旋转。因此，这里没有针对OpenLayers特别需要做的事情，只需应用移动端网页的一般规则即可。<br>移动设备的好处是，我们可以使用GPS或陀螺仪等传感器，我们会将它们当作指南针来使用。</p>
<h4 id="为移动端网页添加一个meta标签"><a href="#为移动端网页添加一个meta标签" class="headerlink" title="为移动端网页添加一个meta标签"></a>为移动端网页添加一个meta标签</h4><p>我们需要在之前已有的<code>index.html</code>中增加了一个额外的<code>meta</code>标签，用于添加视区的设备宽度(<code>device-width</code>)和初始比例(<code>initial-scale</code>)设置：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>OpenLayers<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="keyword">@import</span> <span class="string">&quot;node_modules/ol/ol.css&quot;</span>;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">html</span>, <span class="selector-tag">body</span>, <span class="selector-id">#map-container</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: sans-serif;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;map-container&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./main.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<h4 id="添加导航用的街道地图"><a href="#添加导航用的街道地图" class="headerlink" title="添加导航用的街道地图"></a>添加导航用的街道地图</h4><p>与之前的练习<a href="#%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86">基本地图</a>一样，在<code>main.js</code>添加如下代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="variable constant_">OSM</span> <span class="keyword">from</span> <span class="string">&#x27;ol/source/OSM&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">TileLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/Tile&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Map</span>, <span class="title class_">View</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;ol&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;fromLonLat&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/proj&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">layers</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">      <span class="attr">source</span>: <span class="keyword">new</span> <span class="title function_">OSM</span>(),</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">view</span>: <span class="keyword">new</span> <span class="title class_">View</span>(&#123;</span><br><span class="line">    <span class="attr">center</span>: <span class="title function_">fromLonLat</span>([<span class="number">0</span>, <span class="number">0</span>]),</span><br><span class="line">    <span class="attr">zoom</span>: <span class="number">2</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<h4 id="在移动设备上测试"><a href="#在移动设备上测试" class="headerlink" title="在移动设备上测试"></a>在移动设备上测试</h4><p>由于陀螺仪通常在台式计算机上不可用，因此我们需要在移动设备上测试我们的应用程序。出于安全原因，对地理位置的访问权限，仅授予通过安全连接提供的页面。<br>解决方法是：使用 <a target="_blank" rel="noopener" href="https://ngrok.com/">https://ngrok.com</a> 设置完成后，可以在新终端中使用以下命令为应用程序提供服务：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./ngrok http 5173 --host-header=&quot;localhost:5173&quot;</span><br></pre></td></tr></table></figure>
<p>一切正常后，在移动设备上打开ngrok输出所示的<code>https://</code>页面：<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/mobile/map.jpeg" alt="智能手机上的地图"></p>
<h3 id="地理位置"><a href="#地理位置" class="headerlink" title="地理位置"></a>地理位置</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/mobile/geolocation.html">https://openlayers.org/workshop/en/mobile/geolocation.html</a></p>
<iframe src="https://openlayers.org/workshop/en/mobile/geolocation.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>如果我们想看看在地图上我们所处的位置，那么需要借助浏览器的地理定位API来访问设备的GPS位置(或者没有GPS的设备上的估计位置)。在OpenLayers中，我们可以在地图上用图标可视化该位置。此外，我们还可以在所报告的位置周围显示精度半径。我们也可以添加一个按钮，允许在当前位置使地图居中。<br>首先在main.js中引入需要使用的矢量数据源和图层的类：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">VectorLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/Vector&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VectorSource</span> <span class="keyword">from</span> <span class="string">&#x27;ol/source/Vector&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>然后创建一个Map实例对象：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br></pre></td></tr></table></figure>
<p>接下来，为了要显示的GPS位置，需要创建一个矢量数据源，并将该数据源对象添加到矢量图层对象中，然后将该图层添加到地图：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> source = <span class="keyword">new</span> <span class="title class_">VectorSource</span>();</span><br><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">VectorLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">&#125;);</span><br><span class="line">map.<span class="title function_">addLayer</span>(layer);</span><br></pre></td></tr></table></figure>
<p>现在是时候导入可视化GPS位置所需的要素和几何图形的类了：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Feature</span> <span class="keyword">from</span> <span class="string">&#x27;ol/Feature&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Point</span> <span class="keyword">from</span> <span class="string">&#x27;ol/geom/Point&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;circular&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/geom/Polygon&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>有了这些，我们可以开始编写代码，从而实现通过浏览器的Geolocation API来获取位置及其精度：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">navigator.<span class="property">geolocation</span>.<span class="title function_">watchPosition</span>(</span><br><span class="line">  <span class="keyword">function</span> (<span class="params">pos</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> coords = [pos.<span class="property">coords</span>.<span class="property">longitude</span>, pos.<span class="property">coords</span>.<span class="property">latitude</span>];</span><br><span class="line">    <span class="keyword">const</span> accuracy = <span class="title function_">circular</span>(coords, pos.<span class="property">coords</span>.<span class="property">accuracy</span>);</span><br><span class="line">    source.<span class="title function_">clear</span>(<span class="literal">true</span>);</span><br><span class="line">    source.<span class="title function_">addFeatures</span>([</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">Feature</span>(</span><br><span class="line">        accuracy.<span class="title function_">transform</span>(<span class="string">&#x27;EPSG:4326&#x27;</span>, map.<span class="title function_">getView</span>().<span class="title function_">getProjection</span>())</span><br><span class="line">      ),</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">Feature</span>(<span class="keyword">new</span> <span class="title class_">Point</span>(<span class="title function_">fromLonLat</span>(coords))),</span><br><span class="line">    ]);</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">function</span> (<span class="params">error</span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(<span class="string">`ERROR: <span class="subst">$&#123;error.message&#125;</span>`</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">enableHighAccuracy</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>上面的代码使用了<code>watchPosition()</code>函数，该函数在用户的位置发生变化时会被触发，它将获取纬度、经度和精度，并创建两个要素：具有精确半径的圆和表示该位置的点，这两个要素都从地理坐标转换至地图视图的投影坐标。<br>除此之外，我们还增加了一个错误处理程序，当位置不可用时通知用户，并配置Geolocation API以实现高精度。以使让浏览器获取到确切的GPS位置，而不仅仅是估计的位置。<br>此时，地图虽然已经显示了用户的位置，但是还需要添加一个按钮，使地图在该位置居中。要实现此目的，最简单的方法是使用OpenLayers控件，我们现在将引入该控件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Control</span> <span class="keyword">from</span> <span class="string">&#x27;ol/control/Control&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>接下来，我们将为该控件创建html标签，并注册一个单击侦听器。当单击按钮时，将会在0.5秒的动画中，将地图缩放至表示所处位置的点和精度多边形要素的范围：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> locate = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line">locate.<span class="property">className</span> = <span class="string">&#x27;ol-control ol-unselectable locate&#x27;</span>;</span><br><span class="line">locate.<span class="property">innerHTML</span> = <span class="string">&#x27;&lt;button title=&quot;Locate me&quot;&gt;◎&lt;/button&gt;&#x27;</span>;</span><br><span class="line">locate.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!source.<span class="title function_">isEmpty</span>()) &#123;</span><br><span class="line">    map.<span class="title function_">getView</span>().<span class="title function_">fit</span>(source.<span class="title function_">getExtent</span>(), &#123;</span><br><span class="line">      <span class="attr">maxZoom</span>: <span class="number">18</span>,</span><br><span class="line">      <span class="attr">duration</span>: <span class="number">500</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br><span class="line">map.<span class="title function_">addControl</span>(</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">Control</span>(&#123;</span><br><span class="line">    <span class="attr">element</span>: locate,</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<p>为了将控制按钮放置在缩放按钮下面，我们在<code>index.html</code>的<code>&lt;style&gt;</code>部分添加了几行css样式:</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.locate</span> &#123;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">6em</span>;</span><br><span class="line">  <span class="attribute">left</span>: .<span class="number">5em</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>点击按钮后的结果应该是这样的:<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/mobile/geolocation.jpeg" alt="使用精度多边形进行定位"></p>
<h3 id="指南针"><a href="#指南针" class="headerlink" title="指南针"></a>指南针</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/mobile/compass.html">https://openlayers.org/workshop/en/mobile/compass.html</a></p>
<iframe src="https://openlayers.org/workshop/en/mobile/compass.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>大多数移动设备都配备了陀螺仪，我们将使用它作为指南针，在地图上显示我们的航向。<br>在底层，浏览器可以通过<code>deviceorientation</code>事件访问陀螺仪，侦听器会接收设备三个轴的读数。幸运的是，我们可以利用<a target="_blank" rel="noopener" href="https://npmjs.com/package/kompas/">kompa</a>软件包，直接获取航向。<br>我们想给位置点一个箭头图标，显示航向。<br>首先，我们需要导入的OpenLayers的<code>style</code>模块，以使位置和方向指示器看起来更漂亮：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Fill</span>, <span class="title class_">Icon</span>, <span class="title class_">Style</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/style&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>现在，我们可以创建样式并为图层设置。在此期间，我们不仅为位置和方向创建了一个带有箭头的漂亮图标，而且还可以使表示精度的多边形看起来更好看：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> style = <span class="keyword">new</span> <span class="title class_">Style</span>(&#123;</span><br><span class="line">  <span class="attr">fill</span>: <span class="keyword">new</span> <span class="title class_">Fill</span>(&#123;</span><br><span class="line">    <span class="attr">color</span>: <span class="string">&#x27;rgba(0, 0, 255, 0.2)&#x27;</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">  <span class="attr">image</span>: <span class="keyword">new</span> <span class="title class_">Icon</span>(&#123;</span><br><span class="line">    <span class="attr">src</span>: <span class="string">&#x27;./data/location-heading.svg&#x27;</span>,</span><br><span class="line">    <span class="attr">imgSize</span>: [<span class="number">27</span>, <span class="number">55</span>],</span><br><span class="line">    <span class="attr">rotateWithView</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br><span class="line">layer.<span class="title function_">setStyle</span>(style);</span><br></pre></td></tr></table></figure>
<p>样式包含一个用于精度多边形的填充。对于位置点，我们使用一个<code>svg</code>文件，它位于项目的<code>data/</code>目录中。<code>rotateWithView</code>选项使得图标随着航向的变化和地图视图一块旋转。此时，图标没有旋转，所以箭头将指向上方。<br>接下来，我们将使用<code>kompas</code>从设备定位API获取航向。此包已在项目中安装。如果还没有包含它，你可以通过<code>npm install kopas</code>从命令行或终端安装它。<br>在<code>main.js</code>中导入<code>kopas</code>包：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> kompas <span class="keyword">from</span> <span class="string">&#x27;kompas&#x27;</span>;</span><br></pre></td></tr></table></figure>
<p>最后要做的是借助kompa工具来获取航向，并用航向为图标设置旋转角度:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">startCompass</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">kompas</span>()</span><br><span class="line">    .<span class="title function_">watch</span>()</span><br><span class="line">    .<span class="title function_">on</span>(<span class="string">&#x27;heading&#x27;</span>, <span class="keyword">function</span> (<span class="params">heading</span>) &#123;</span><br><span class="line">      style.<span class="title function_">getImage</span>().<span class="title function_">setRotation</span>((<span class="title class_">Math</span>.<span class="property">PI</span> / <span class="number">180</span>) * heading);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (</span><br><span class="line">  <span class="variable language_">window</span>.<span class="property">DeviceOrientationEvent</span> &amp;&amp;</span><br><span class="line">  <span class="keyword">typeof</span> <span class="title class_">DeviceOrientationEvent</span>.<span class="property">requestPermission</span> === <span class="string">&#x27;function&#x27;</span></span><br><span class="line">) &#123;</span><br><span class="line">  locate.<span class="title function_">addEventListener</span>(<span class="string">&#x27;click&#x27;</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title class_">DeviceOrientationEvent</span>.<span class="title function_">requestPermission</span>()</span><br><span class="line">      .<span class="title function_">then</span>(startCompass)</span><br><span class="line">      .<span class="title function_">catch</span>(<span class="keyword">function</span> (<span class="params">error</span>) &#123;</span><br><span class="line">        <span class="title function_">alert</span>(<span class="string">`ERROR: <span class="subst">$&#123;error.message&#125;</span>`</span>);</span><br><span class="line">      &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="string">&#x27;ondeviceorientationabsolute&#x27;</span> <span class="keyword">in</span> <span class="variable language_">window</span>) &#123;</span><br><span class="line">  <span class="title function_">startCompass</span>();</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="title function_">alert</span>(<span class="string">&#x27;No device orientation provided by device&#x27;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>用户查找方向的最终导航工具现在应该如下所示：<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/mobile/compass.gif" alt="用户用导航工具环顾四周"></p>
<h2 id="GeoTIFF渲染"><a href="#GeoTIFF渲染" class="headerlink" title="GeoTIFF渲染"></a>GeoTIFF渲染</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/cog/">https://openlayers.org/workshop/en/cog/</a></p>
<iframe src="https://openlayers.org/workshop/en/cog/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>在本小节内容中，我们将演示如何通过可视化Cloud-Optimized GeoTIFF(COG)的数据去渲染一张地图。除了基本的全分辨率图像之外，GeoTIFF(或任何TIFF)图像还允许具有附加的概览图像。此外，图像的内部像素布局可以是平铺的(我理解的是一块一块的)，而不是以条带(一条一条的)组织的。Cloud-Optimized GeoTIFF格式更提倡使用常规的平铺布局和内置的概览视图来托管数据，使客户端更高效地渲染图像的一小部分(只读取所需的切片，而不是整个图像)或较低分辨率的概览。<br>OpenLayers的<code>ol/source/GeoTIFF</code>的数据源对象，可以从一个或多个远程托管的GeoTIFF数据源读取多波段数据，接下来将演练<strong>渲染单个多波段图像、渲染多个单波段图像以及对输入数据执行简单的波段计算</strong>。</p>
<h3 id="真彩色GeoTIFF"><a href="#真彩色GeoTIFF" class="headerlink" title="真彩色GeoTIFF"></a>真彩色GeoTIFF</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/cog/true-color.html">https://openlayers.org/workshop/en/cog/true-color.html</a></p>
<iframe src="https://openlayers.org/workshop/en/cog/true-color.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p><a target="_blank" rel="noopener" href="https://sentinel.esa.int/web/sentinel/missions/sentinel-2">Sentinel-2</a>(哨兵2号)卫星的任务是收集和传播覆盖地球陆地表面的图像，重访周期为2至5天，其传感器收集了多个波段的图像，其中每个波段是电磁波谱的一部分。2A(L2A)级产品提供以下波段的表面反射率测量：</p>
<table>
<thead>
<tr>
<th>Band</th>
<th>Description</th>
<th>Central Wavelength (μm)</th>
<th>Resolution (m)</th>
</tr>
</thead>
<tbody><tr>
<td>B01</td>
<td>Coastal aerosol</td>
<td>0.433</td>
<td>60</td>
</tr>
<tr>
<td>B02</td>
<td>Blue</td>
<td>0.460</td>
<td>10</td>
</tr>
<tr>
<td>B03</td>
<td>Green</td>
<td>0.560</td>
<td>10</td>
</tr>
<tr>
<td>B04</td>
<td>Red</td>
<td>0.665</td>
<td>10</td>
</tr>
<tr>
<td>B05</td>
<td>Vegetation red edge</td>
<td>0.705</td>
<td>20</td>
</tr>
<tr>
<td>B06</td>
<td>Vegetation red edge</td>
<td>0.740</td>
<td>20</td>
</tr>
<tr>
<td>B07</td>
<td>Vegetation red edge</td>
<td>0.783</td>
<td>20</td>
</tr>
<tr>
<td>B08</td>
<td>Near-infrared</td>
<td>0.842</td>
<td>10</td>
</tr>
<tr>
<td>B09</td>
<td>Water vapor</td>
<td>0.945</td>
<td>60</td>
</tr>
<tr>
<td>B10</td>
<td>Short-wave infrared - Cirrus</td>
<td>1.375</td>
<td>60</td>
</tr>
<tr>
<td>B11</td>
<td>Short-wave infrared</td>
<td>1.610</td>
<td>20</td>
</tr>
<tr>
<td>B12</td>
<td>Short-wave infrared</td>
<td>2.190</td>
<td>20</td>
</tr>
</tbody></table>
<p>当查看包含来自可见光波谱之外的数据的多波段图像时，我们必须选择如何将每个波段映射到可在数字显示器上渲染的三个可见通道(红色、绿色或蓝色)之一。真彩色合成是一种渲染，在蓝色通道中显示可见蓝色(来自Sentinel-2的B02)，在绿色通道中显示可见绿色(B03)，在红色通道中显示可见红色(B04)，任何其他卫星图像波段到显示频道的映射都是假彩色合成。</p>
<p>在<a target="_blank" rel="noopener" href="https://registry.opendata.aws/sentinel-2/">Amazon S3</a>上有一系列被作为Cloud-Optimized GeoTIFF数据来托管的Sentinel-2 L2A产品。在本练习中，我们将在地图上渲染其中一个。</p>
<p>首先，修改<code>index.html</code>的代码，以便开始渲染一整幅地图：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>OpenLayers<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="keyword">@import</span> <span class="string">&quot;node_modules/ol/ol.css&quot;</span>;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">html</span>, <span class="selector-tag">body</span>, <span class="selector-id">#map-container</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: sans-serif;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;map-container&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./main.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>现在我们将导入两个以前没有使用过的新组件：</p>
<ul>
<li><code>ol/source/GeoTIFF</code> 数据源对象：用于处理多波段栅格数据。</li>
<li><code>ol/layer/WebGLTile</code> 图层对象：使用GPU上的着色器操作数据切片。</li>
</ul>
<p>更新<code>main.js</code>以在地图上加载和渲染远程托管的GeoTIFF文件：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">GeoTIFF</span> <span class="keyword">from</span> <span class="string">&#x27;ol/source/GeoTIFF.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Map</span> <span class="keyword">from</span> <span class="string">&#x27;ol/Map.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Projection</span> <span class="keyword">from</span> <span class="string">&#x27;ol/proj/Projection.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">TileLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/WebGLTile.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">View</span> <span class="keyword">from</span> <span class="string">&#x27;ol/View.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;getCenter&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/extent.js&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> projection = <span class="keyword">new</span> <span class="title class_">Projection</span>(&#123;</span><br><span class="line">  <span class="attr">code</span>: <span class="string">&#x27;EPSG:32721&#x27;</span>,</span><br><span class="line">  <span class="attr">units</span>: <span class="string">&#x27;m&#x27;</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// metadata from https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/S2B_21HUB_20210915_0_L2A.json</span></span><br><span class="line"><span class="keyword">const</span> sourceExtent = [<span class="number">300000</span>, <span class="number">6090260</span>, <span class="number">409760</span>, <span class="number">6200020</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> source = <span class="keyword">new</span> <span class="title class_">GeoTIFF</span>(&#123;</span><br><span class="line">  <span class="attr">sources</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">url</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/TCI.tif&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  ],</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">layers</span>: [layer],</span><br><span class="line">  <span class="attr">view</span>: <span class="keyword">new</span> <span class="title class_">View</span>(&#123;</span><br><span class="line">    <span class="attr">projection</span>: projection,</span><br><span class="line">    <span class="attr">center</span>: <span class="title function_">getCenter</span>(sourceExtent),</span><br><span class="line">    <span class="attr">extent</span>: sourceExtent,</span><br><span class="line">    <span class="attr">zoom</span>: <span class="number">1</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>运行项目代码，结果如下图所示，在WebGL切片图层中渲染了GeoTIFF的地图。</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/cog/true-color.png" alt="Sentinel-2 GeoTIFF的真彩色渲染"></p>
<p>这里最棘手的部分是找到你可能感兴趣的图像的URL，要做到这一点，你可以尝试在 <a target="_blank" rel="noopener" href="https://apps.sentinel-hub.com/eo-browser/">EO (Earth Observation) Browser</a>中搜索。如果你安装了<code>aws</code>命令行界面，你还可以列出<code>s3://seninel-cogs/</code>存储桶内容，以通过Sentinel-2网格单元标识符和日期获取图像所在的路径。例如，要搜索2021年9月布宜诺斯艾利斯周围的影像：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">aws s3 ls s3://sentinel-cogs/sentinel-s2-l2a-cogs/21/H/UB/2021/9/ --no-sign-request</span><br></pre></td></tr></table></figure>

<p>接下来最困难的部分是：找出适合地图视图的投影和范围。在下一小节中，我们将使这一点变得更容易。</p>
<h3 id="简化地图视图的配置"><a href="#简化地图视图的配置" class="headerlink" title="简化地图视图的配置"></a>简化地图视图的配置</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/cog/simplified-view.html">https://openlayers.org/workshop/en/cog/simplified-view.html</a></p>
<iframe src="https://openlayers.org/workshop/en/cog/simplified-view.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在前面的示例中，我们不得不配置与地图视图有关的图像的空间参考系统和坐标位置的信息。</p>
<p>首先我们需要知道的是<strong>空间参考系统</strong>的标识符，这个标识符用于创建OpenLayers投影(还需要为它配置单位)：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> projection = <span class="keyword">new</span> <span class="title class_">Projection</span>(&#123;</span><br><span class="line">  <span class="attr">code</span>: <span class="string">&#x27;EPSG:32721&#x27;</span>,</span><br><span class="line">  <span class="attr">units</span>: <span class="string">&#x27;m&#x27;</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>然后，关于图像我们还需要知道的是它的坐标位置—用于创建边界框或范围数组：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// metadata from https://s3.us-west-2.amazonaws.com/sentinel-cogs/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/S2B_21HUB_20210915_0_L2A.json</span></span><br><span class="line"><span class="keyword">const</span> extent = [<span class="number">300000</span>, <span class="number">6090260</span>, <span class="number">409760</span>, <span class="number">6200020</span>];</span><br></pre></td></tr></table></figure>

<p>有了这些信息，我们终于能够配置地图的视图：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">layers</span>: [layer],</span><br><span class="line">  <span class="attr">view</span>: <span class="keyword">new</span> <span class="title class_">View</span>(&#123;</span><br><span class="line">    <span class="attr">projection</span>: projection,</span><br><span class="line">    <span class="attr">center</span>: <span class="title function_">getCenter</span>(extent),</span><br><span class="line">    <span class="attr">extent</span>: extent,</span><br><span class="line">    <span class="attr">zoom</span>: <span class="number">1</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>GeoTIFF Imagery使用特殊的“geo”标签扩展了常规的TIFF图像，这些标签提供了有关图像的空间参考系统和坐标位置等信息。OpenLayers中的<code>ol/source/GeoTIFF</code>数据源能够解析此信息，我们理所当然地将它用于配置地图的视图。</p>
<p>GeoTIFF数据源对象的<code>source.getView()</code>方法，会返回解析的GeoTIFF元数据的视图属性(如投影、中心、范围和缩放)。地图的构造函数现在接受一个视图选项，该选项可以是上述这些属性。因此，我们可以为地图提供来自数据源的视图属性，而不是自己在元数据中挖掘以查找投影和范围之类的内容。</p>
<p>更新<code>main.js</code>，以便在map构造函数使用从数据源获取视图属性：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">layers</span>: [layer],</span><br><span class="line">  <span class="attr">view</span>: source.<span class="title function_">getView</span>(),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>现在你可以从上一小节的<code>main.js</code>文件中删除投影、范围和相关的导入(View、projection和getCenter)。</p>
<p>此时你会发现，我们用了更少的代码实现了相同的结果：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/cog/true-color.png" alt="Sentinel-2 GeoTIFF的真彩色渲染"></p>
<h3 id="假彩色合成"><a href="#假彩色合成" class="headerlink" title="假彩色合成"></a>假彩色合成</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/cog/false-color.html">https://openlayers.org/workshop/en/cog/false-color.html</a></p>
<iframe src="https://openlayers.org/workshop/en/cog/false-color.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在上一小节中，我们使用<code>ol/source/GeoTIFF</code>数据源对象从单个多波段数据源(具有红、绿、蓝和α波段)渲染真彩色图像。在这个例子中，我们将从可见波谱之外拉入数据，并使用它来渲染假彩色合成。</p>
<p>我们将要渲染一个假彩色合成，突出裸露土壤区域的植被。富含叶绿素的植被和它在可见光波长上的反射率相比，它在波谱的近红外(Sentinel-2 B08)部分是明亮的。相比之下，裸露的土壤则在近红外区的亮度不如其在可见光波段的反射率。用Sentinel-2波段显示的绿色植被和裸露土壤的反射波谱见下图。</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/cog/spectra.png" alt="植被(绿色)、土壤(红色)和水(蓝色)的波谱特征。"></p>
<p>为了在多波谱图像中突出植被，通常在红色通道中显示近红外(B08)反射率，在绿色通道中显示红色反射率(B04)，在蓝色通道中显示绿色反射率(B03)。我们可以使用<code>ol/source/GeoTIFF</code>数据源以RGB顺序加载三个单独的单波段GeoTIFF图像来完成此操作。</p>
<p>更新<code>main.js</code>代码:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> source = <span class="keyword">new</span> <span class="title class_">GeoTIFF</span>(&#123;</span><br><span class="line">  <span class="attr">sources</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">// near-infrared(近红外) reflectance</span></span><br><span class="line">      <span class="attr">url</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/B08.tif&#x27;</span>,</span><br><span class="line">      <span class="attr">max</span>: <span class="number">5000</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">// red reflectance</span></span><br><span class="line">      <span class="attr">url</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/B04.tif&#x27;</span>,</span><br><span class="line">      <span class="attr">max</span>: <span class="number">5000</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">// green reflectance</span></span><br><span class="line">      <span class="attr">url</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/B03.tif&#x27;</span>,</span><br><span class="line">      <span class="attr">max</span>: <span class="number">5000</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  ],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>运行项目，查看假彩色合成的结果：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/cog/false-color.png" alt="Sentinel-2 GeoTIFF的假彩色渲染"></p>
<h3 id="波段计算"><a href="#波段计算" class="headerlink" title="波段计算"></a>波段计算</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/cog/ndvi.html">https://openlayers.org/workshop/en/cog/ndvi.html</a></p>
<iframe src="https://openlayers.org/workshop/en/cog/ndvi.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在之前的内容中，我们已经看到了<code>ol/source/GeoTIFF</code>数据源如何用于渲染真彩色和假彩色合成：通过将缩放的反射率值直接渲染到红色、绿色或蓝色显示通道之一来实现这一点。除此之外，还可以对来自GeoTIFF(或其他数据的切片数据源)的反射率值执行计算，并将输出值映射到RGBA颜色值。</p>
<p><code>ol/layer/WebGLTile</code>图层对象接受可用于控制数据源渲染的<code>style</code>属性。在本例中，我们将使用前面的示例中使用过的Sentinel-2数据，并借助数学表达式来计算归一化植被指数(或植被覆盖指数，NDVI)。</p>
<p>NDVI是近红外(NIR)和红色之间的差值与近红外和红色反射率值之和的比率。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">NDVI = (NIR - RED) / (NIR + RED)</span><br></pre></td></tr></table></figure>

<p>这种归一化差异提供了绿色植被密度或健康的指数。在将近红外和红色反射率的差除以其总和时，根据亮度或照度的变化对指数进行归一化。我们的想法是，植被覆盖的阳坡应该与植被覆盖的阴坡具有类似的指数。</p>
<p>要渲染NDVI的值，我们需要使用近红外(B08)和红色(B04)波段来配置数据源对象，更新<code>main.js</code>脚本，使源代码如下所示：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> source = <span class="keyword">new</span> <span class="title class_">GeoTIFF</span>(&#123;</span><br><span class="line">  <span class="attr">sources</span>: [</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">// red reflectance</span></span><br><span class="line">      <span class="attr">url</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/B04.tif&#x27;</span>,</span><br><span class="line">      <span class="attr">max</span>: <span class="number">10000</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="comment">// near-infrared(近红外) reflectance</span></span><br><span class="line">      <span class="attr">url</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A/B08.tif&#x27;</span>,</span><br><span class="line">      <span class="attr">max</span>: <span class="number">10000</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  ],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>接下来，我们将根据数据源的输入波段来创建表达式，然后用表达式来计算NDVI。将以下变量添加到<code>main.js</code>中：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// near-infrared(近红外) is the second band from above</span></span><br><span class="line"><span class="keyword">const</span> nir = [<span class="string">&#x27;band&#x27;</span>, <span class="number">2</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// red is the first band from above</span></span><br><span class="line"><span class="keyword">const</span> red = [<span class="string">&#x27;band&#x27;</span>, <span class="number">1</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> difference = [<span class="string">&#x27;-&#x27;</span>, nir, red];</span><br><span class="line"><span class="keyword">const</span> sum = [<span class="string">&#x27;+&#x27;</span>, nir, red];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> ndvi = [<span class="string">&#x27;/&#x27;</span>, difference, sum];</span><br></pre></td></tr></table></figure>

<p>上面的表达式使用了基于数组的语法：<code>[运算符，...参数]</code>。波段运算符从<code>ol/source/GeoTIFF</code>的数据源列表中访问波段-其中第一个配置的波段为1，第二个为2，依此类推。<code>-</code>、<code>+</code>和<code>/</code>运算符计算其输入参数的差、和和比率。NDVI表达式将产生一个介于-1(植物不是很多)和1(植物很多)之间的值。下一步是将这些值映射到颜色值上。</p>
<p><code>ol/layer/WebGLTile</code>图层的<code>style</code>属性中有一个<code>color</code>属性，它决定了每个像素的最终输出颜色。我们想在非植被(棕色)和植被(绿色)NDVI值之间插入颜色，为此，我们使用<code>interpolate</code>(插值)运算符，在一些停止值之间应用<code>linear</code>(线性)插值。</p>
<p>编辑你的<code>main.js</code>代码如下:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">  <span class="attr">style</span>: &#123;</span><br><span class="line">    <span class="attr">color</span>: [</span><br><span class="line">      <span class="string">&#x27;interpolate&#x27;</span>,</span><br><span class="line">      [<span class="string">&#x27;linear&#x27;</span>],</span><br><span class="line">      ndvi,</span><br><span class="line">      -<span class="number">0.2</span>, <span class="comment">// ndvi values &lt;= -0.2 will get the color below</span></span><br><span class="line">      [<span class="number">191</span>, <span class="number">191</span>, <span class="number">191</span>],</span><br><span class="line">      <span class="number">0</span>, <span class="comment">// ndvi values between -0.2 and 0 will get an interpolated color between the one above and the one below</span></span><br><span class="line">      [<span class="number">255</span>, <span class="number">255</span>, <span class="number">224</span>],</span><br><span class="line">      <span class="number">0.2</span>,</span><br><span class="line">      [<span class="number">145</span>, <span class="number">191</span>, <span class="number">82</span>],</span><br><span class="line">      <span class="number">0.4</span>,</span><br><span class="line">      [<span class="number">79</span>, <span class="number">138</span>, <span class="number">46</span>],</span><br><span class="line">      <span class="number">0.6</span>,</span><br><span class="line">      [<span class="number">15</span>, <span class="number">84</span>, <span class="number">10</span>],</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>如果一切顺利，NDVI可视化效果如下：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/cog/ndvi.png" alt="由Sentinel-2 GeoTIFF计算生成的NDVI"></p>
<p>选择这些停靠值和颜色是一项艰巨的工作，接下来我们将借助第三方库来帮助我们做这项工作。</p>
<h3 id="colormap程序包"><a href="#colormap程序包" class="headerlink" title="colormap程序包"></a>colormap程序包</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/cog/colormap.html">https://openlayers.org/workshop/en/cog/colormap.html</a></p>
<iframe src="https://openlayers.org/workshop/en/cog/colormap.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p><a target="_blank" rel="noopener" href="https://www.npmjs.com/package/colormap">colormap</a>程序包是一个很好的创建色彩映射表的实用程序库，此库已作为项目的依赖项被添加。要导入它，请编辑<code>main.js</code>如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">getColorStops</span>(<span class="params">name, min, max, steps, reverse</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> delta = (max - min) / (steps - <span class="number">1</span>);</span><br><span class="line">  <span class="keyword">const</span> stops = <span class="keyword">new</span> <span class="title class_">Array</span>(steps * <span class="number">2</span>);</span><br><span class="line">  <span class="keyword">const</span> colors = <span class="title function_">colormap</span>(&#123;<span class="attr">colormap</span>: name, <span class="attr">nshades</span>: steps, <span class="attr">format</span>: <span class="string">&#x27;rgba&#x27;</span>&#125;);</span><br><span class="line">  <span class="keyword">if</span> (reverse) &#123;</span><br><span class="line">    colors.<span class="title function_">reverse</span>();</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; steps; i++) &#123;</span><br><span class="line">    stops[i * <span class="number">2</span>] = min + i * delta;</span><br><span class="line">    stops[i * <span class="number">2</span> + <span class="number">1</span>] = colors[i];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> stops;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在我们可以修改图层样式的<code>color</code>表达式，以使用一组停靠值和颜色值，编辑<code>main.js</code>中的图层定义以使用我们的新函数：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">  <span class="attr">style</span>: &#123;</span><br><span class="line">    <span class="attr">color</span>: [</span><br><span class="line">      <span class="string">&#x27;interpolate&#x27;</span>,</span><br><span class="line">      [<span class="string">&#x27;linear&#x27;</span>],</span><br><span class="line">      ndvi,</span><br><span class="line">      <span class="comment">// color ramp(渐变) for NDVI values</span></span><br><span class="line">      ...<span class="title function_">getColorStops</span>(<span class="string">&#x27;earth&#x27;</span>, -<span class="number">0.5</span>, <span class="number">1</span>, <span class="number">10</span>, <span class="literal">true</span>),</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>使用新的彩色映射应用到NDVI输出，效果如下：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/cog/colormap.png" alt="由Sentinel-2 GeoTIFF计算生成的NDVI"></p>
<h3 id="添加下拉框"><a href="#添加下拉框" class="headerlink" title="添加下拉框"></a>添加下拉框</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/cog/visualizations.html">https://openlayers.org/workshop/en/cog/visualizations.html</a></p>
<iframe src="https://openlayers.org/workshop/en/cog/visualizations.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在前面的示例中，我们已经看到了同一个Sentinel-2图像的真彩色合成、假彩色合成和NDVI渲染。如果用户可以在不需要每次都更改代码的情况下从这些可视化效果中选择一种或更多，那就太好了。为此，我们将创建一个可用的可视化的列表，并向我们的页面添加一个<code>&lt;select&gt;</code>元素(即下拉框)，让用户选择要显示的内容。</p>
<p>除了真彩色、假彩色和NDVI可视化之外，我们还将添加一个新的归一化水指数(NDWI)，它NDVI类似，不同之处在于它可以用于监测水体的变化。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">NDWI = (GREEN - NIR) / (GREEN + NIR)</span><br></pre></td></tr></table></figure>

<p>正如我们已经看到的，每个可视化都需要有一组数据源(这些是用于单波段或多波段GeoTIFF的URL)、用于缩放GeoTIFF值的可选<code>max</code>值以及用于渲染图层的可选<code>style</code>。此外，我们将为每个可视化显示一个<code>name</code>。</p>
<p>编辑<code>main.js</code>代码如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> visualizations = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;True Color&#x27;</span>,</span><br><span class="line">    <span class="attr">sources</span>: [<span class="string">&#x27;TCI&#x27;</span>],</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;False Color&#x27;</span>,</span><br><span class="line">    <span class="attr">sources</span>: [<span class="string">&#x27;B08&#x27;</span>, <span class="string">&#x27;B04&#x27;</span>, <span class="string">&#x27;B03&#x27;</span>],</span><br><span class="line">    <span class="attr">max</span>: <span class="number">5000</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;NDVI&#x27;</span>,</span><br><span class="line">    <span class="attr">sources</span>: [<span class="string">&#x27;B04&#x27;</span>, <span class="string">&#x27;B08&#x27;</span>],</span><br><span class="line">    <span class="attr">max</span>: <span class="number">10000</span>,</span><br><span class="line">    <span class="attr">style</span>: &#123;</span><br><span class="line">      <span class="attr">color</span>: [</span><br><span class="line">        <span class="string">&#x27;interpolate&#x27;</span>,</span><br><span class="line">        [<span class="string">&#x27;linear&#x27;</span>],</span><br><span class="line">        [<span class="string">&#x27;/&#x27;</span>, [<span class="string">&#x27;-&#x27;</span>, [<span class="string">&#x27;band&#x27;</span>, <span class="number">2</span>], [<span class="string">&#x27;band&#x27;</span>, <span class="number">1</span>]], [<span class="string">&#x27;+&#x27;</span>, [<span class="string">&#x27;band&#x27;</span>, <span class="number">2</span>], [<span class="string">&#x27;band&#x27;</span>, <span class="number">1</span>]]],</span><br><span class="line">        ...<span class="title function_">getColorStops</span>(<span class="string">&#x27;earth&#x27;</span>, -<span class="number">0.5</span>, <span class="number">1</span>, <span class="number">10</span>, <span class="literal">true</span>),</span><br><span class="line">      ],</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;NDWI&#x27;</span>,</span><br><span class="line">    <span class="attr">sources</span>: [<span class="string">&#x27;B03&#x27;</span>, <span class="string">&#x27;B08&#x27;</span>],</span><br><span class="line">    <span class="attr">max</span>: <span class="number">10000</span>,</span><br><span class="line">    <span class="attr">style</span>: &#123;</span><br><span class="line">      <span class="attr">color</span>: [</span><br><span class="line">        <span class="string">&#x27;interpolate&#x27;</span>,</span><br><span class="line">        [<span class="string">&#x27;linear&#x27;</span>],</span><br><span class="line">        [<span class="string">&#x27;/&#x27;</span>, [<span class="string">&#x27;-&#x27;</span>, [<span class="string">&#x27;band&#x27;</span>, <span class="number">1</span>], [<span class="string">&#x27;band&#x27;</span>, <span class="number">2</span>]], [<span class="string">&#x27;+&#x27;</span>, [<span class="string">&#x27;band&#x27;</span>, <span class="number">1</span>], [<span class="string">&#x27;band&#x27;</span>, <span class="number">2</span>]]],</span><br><span class="line">        ...<span class="title function_">getColorStops</span>(<span class="string">&#x27;viridis&#x27;</span>, -<span class="number">1</span>, <span class="number">1</span>, <span class="number">10</span>, <span class="literal">true</span>),</span><br><span class="line">      ],</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure>

<p>现在，我们不是一次创建我们的GeoTIFF数据源和图层，而是在用户选择下拉框的可视化选项时借助函数创建它们。此函数的参数为<code>base</code>(即url的基础部分)和<code>visualization</code>，它会返回一个图层。编辑<code>main.js</code>以删除数据源和图层定义，并添加此函数：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">createLayer</span>(<span class="params">base, visualization</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> source = <span class="keyword">new</span> <span class="title class_">GeoTIFF</span>(&#123;</span><br><span class="line">    <span class="attr">sources</span>: visualization.<span class="property">sources</span>.<span class="title function_">map</span>(<span class="function">(<span class="params">id</span>) =&gt;</span> (&#123;</span><br><span class="line">      <span class="attr">url</span>: <span class="string">`<span class="subst">$&#123;base&#125;</span>/<span class="subst">$&#123;id&#125;</span>.tif`</span>,</span><br><span class="line">      <span class="attr">max</span>: visualization.<span class="property">max</span>,</span><br><span class="line">    &#125;)),</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">    <span class="attr">source</span>: source,</span><br><span class="line">    <span class="attr">style</span>: visualization.<span class="property">style</span>,</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接下来，我们可以更改<code>main.js</code>中的地图对象的定义，使其不包含任何图层(这些图层将在用户选择下拉框的可视化选项时添加)：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>现在我们需要一种让用户选择显示哪种可视化方法的控件，为此，我们将在<code>index.html</code>中<code>&lt;script&gt;</code>标签之前添加一个下拉框:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;controls&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;visualization&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>要使下拉框显示在地图的右上角，请将以下样式添加到<code>index.html</code>中的<code>&lt;style&gt;</code>标签中：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#controls</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">right</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>准备好<code>&lt;select&gt;</code>元素后，我们需要将每个可视化名称添加到下拉框的<code>&lt;option&gt;</code>标签中。为此，需要将以下代码添加到<code>main.js</code>中：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> visualizationSelector = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;visualization&#x27;</span>);</span><br><span class="line">visualizations.<span class="title function_">forEach</span>(<span class="function">(<span class="params">visualization</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> option = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;option&#x27;</span>);</span><br><span class="line">  option.<span class="property">textContent</span> = visualization.<span class="property">name</span>;</span><br><span class="line">  visualizationSelector.<span class="title function_">appendChild</span>(option);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>最后，我们将创建一个函数，根据选定的可视化效果使用新的图层更新地图。我们将为下拉框添加此函数作为<code>change</code>侦听器，并调用它来初始化应用程序：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">updateVisualization</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> visualization = visualizations[visualizationSelector.<span class="property">selectedIndex</span>];</span><br><span class="line">  <span class="keyword">const</span> base =</span><br><span class="line">    <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A&#x27;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> layer = <span class="title function_">createLayer</span>(base, visualization);</span><br><span class="line">  map.<span class="title function_">setLayers</span>([layer]);</span><br><span class="line"></span><br><span class="line">  map.<span class="title function_">setView</span>(layer.<span class="title function_">getSource</span>().<span class="title function_">getView</span>());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">visualizationSelector.<span class="title function_">addEventListener</span>(<span class="string">&#x27;change&#x27;</span>, updateVisualization);</span><br><span class="line"><span class="title function_">updateVisualization</span>();</span><br></pre></td></tr></table></figure>

<p>运行代码后效果如下：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/cog/visualizations.png" alt="为多种类型的可视化添加一个下拉框"></p>
<details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/cog/viz-plus.html">https://openlayers.org/workshop/en/cog/viz-plus.html</a></p>
<iframe src="https://openlayers.org/workshop/en/cog/viz-plus.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>接下来要实现让用户可以选择图像数据源。首先需要列出一个图像列表，对于每张图像，我们希望在下拉框中显示图像名称，修改<code>main.js</code>如下:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> images = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;Buenos Aires&#x27;</span>,</span><br><span class="line">    <span class="attr">base</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/21/H/UB/2021/9/S2B_21HUB_20210915_0_L2A&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;Minneapolis&#x27;</span>,</span><br><span class="line">    <span class="attr">base</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/15/T/WK/2021/9/S2B_15TWK_20210918_0_L2A&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&#x27;Cape Town&#x27;</span>,</span><br><span class="line">    <span class="attr">base</span>: <span class="string">&#x27;https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/34/H/BH/2021/9/S2B_34HBH_20210922_0_L2A&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure>

<p>接下来，我们将添加另一个下拉框，让用户选择图像，并在index.html中，将控件调整为如下所示：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;controls&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;image&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;visualization&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>我们需要为每个图像源使用一个<code>&lt;option&gt;</code>标签并添加到下拉框中，在main.js中添加以下内容：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> imageSelector = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;image&#x27;</span>);</span><br><span class="line">images.<span class="title function_">forEach</span>(<span class="function">(<span class="params">image</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> option = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;option&#x27;</span>);</span><br><span class="line">  option.<span class="property">textContent</span> = image.<span class="property">name</span>;</span><br><span class="line">  imageSelector.<span class="title function_">appendChild</span>(option);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>接下来只需对更新可视化的函数做一个微小的调整，以便它从所选图像源获取<code>base</code>(即url的基础部分)。在<code>main.js</code>中编辑<code>updateVisualization</code>函数及其周围的行:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> previousBase;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">updateVisualization</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> visualization = visualizations[visualizationSelector.<span class="property">selectedIndex</span>];</span><br><span class="line">  <span class="keyword">const</span> base = images[imageSelector.<span class="property">selectedIndex</span>].<span class="property">base</span>;</span><br><span class="line">  <span class="keyword">const</span> newBase = base !== previousBase;</span><br><span class="line">  previousBase = base;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> layer = <span class="title function_">createLayer</span>(base, visualization);</span><br><span class="line">  map.<span class="title function_">setLayers</span>([layer]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (newBase) &#123;</span><br><span class="line">    map.<span class="title function_">setView</span>(layer.<span class="title function_">getSource</span>().<span class="title function_">getView</span>());</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">visualizationSelector.<span class="title function_">addEventListener</span>(<span class="string">&#x27;change&#x27;</span>, updateVisualization);</span><br><span class="line">imageSelector.<span class="title function_">addEventListener</span>(<span class="string">&#x27;change&#x27;</span>, updateVisualization);</span><br><span class="line"><span class="title function_">updateVisualization</span>();</span><br></pre></td></tr></table></figure>

<p>运行结果如下：如果用户选择了新的图像源或可视化类型，都会更新视图。<br><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/cog/viz-plus.png"></p>
<h2 id="矢量切片和Mapbox样式"><a href="#矢量切片和Mapbox样式" class="headerlink" title="矢量切片和Mapbox样式"></a>矢量切片和Mapbox样式</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vectortile/">https://openlayers.org/workshop/en/vectortile/</a></p>
<iframe src="https://openlayers.org/workshop/en/vectortile/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>在本节内容中，我们将学习有关在OpenLayers中使用矢量切片的所有内容，并引入<a target="_blank" rel="noopener" href="https://npmjs.com/package/ol-mapbox-style">ol-mapbox-style</a>实用程序来处理Mapbox Style文件。</p>
<h3 id="矢量切片图层"><a href="#矢量切片图层" class="headerlink" title="矢量切片图层"></a>矢量切片图层</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vectortile/map.html">https://openlayers.org/workshop/en/vectortile/map.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vectortile/map.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>我们现在知道了如何加载切片影像，并且我们学习了加载和渲染矢量数据的不同方法。但是，若切片数据不仅可以快速地传输到浏览器上，并且可以动态地设置样式，那会怎么样？这就是矢量切片的用途。OpenLayers通过<code>VectorTile</code>图层对象来支持矢量切片。</p>
<h4 id="使用矢量数据渲染世界地图"><a href="#使用矢量数据渲染世界地图" class="headerlink" title="使用矢量数据渲染世界地图"></a>使用矢量数据渲染世界地图</h4><p><code>index.html</code>界面布局如下：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>OpenLayers<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="keyword">@import</span> <span class="string">&quot;node_modules/ol/ol.css&quot;</span>;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">html</span>, <span class="selector-tag">body</span>, <span class="selector-id">#map-container</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: sans-serif;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;map-container&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./main.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>在<code>main.js</code>中引入所需的包：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="variable constant_">MVT</span> <span class="keyword">from</span> <span class="string">&#x27;ol/format/MVT&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VectorTileLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/VectorTile&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VectorTileSource</span> <span class="keyword">from</span> <span class="string">&#x27;ol/source/VectorTile&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Map</span>, <span class="title class_">View</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;ol&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;fromLonLat&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/proj&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p>我们要使用的数据源是来自Natural Earth数据的世界各国的简单地图，并由Geoserver提供矢量切片。</p>
<p>创建一个map对象：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">view</span>: <span class="keyword">new</span> <span class="title class_">View</span>(&#123;</span><br><span class="line">    <span class="attr">center</span>: <span class="title function_">fromLonLat</span>([<span class="number">0</span>, <span class="number">0</span>]),</span><br><span class="line">    <span class="attr">zoom</span>: <span class="number">2</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>我们这次使用的图层类型是<code>VectorTileLayer</code>，并使用<code>VectorTileSource</code>：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">VectorTileLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: <span class="keyword">new</span> <span class="title class_">VectorTileSource</span>(&#123;</span><br><span class="line">    <span class="attr">format</span>: <span class="keyword">new</span> <span class="title function_">MVT</span>(),</span><br><span class="line">    <span class="attr">url</span>:</span><br><span class="line">      <span class="string">&#x27;https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/&#x27;</span> +</span><br><span class="line">      <span class="string">&#x27;ne:ne_10m_admin_0_countries@EPSG%3A900913@pbf/&#123;z&#125;/&#123;x&#125;/&#123;-y&#125;.pbf&#x27;</span>,</span><br><span class="line">    <span class="attr">maxZoom</span>: <span class="number">14</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br><span class="line">map.<span class="title function_">addLayer</span>(layer);</span><br></pre></td></tr></table></figure>

<p>上述提供的数据源仅支持从0到14的缩放级别，因此我们需要将此信息传递给数据源。矢量切片图层通常针对512像素的切片大小进行优化，这也是<code>VectorTile</code>数据源的切片网格的默认大小。数据提供程序要求我们显示一些属性，我们也将这些属性添加到数据源对象的配置中。</p>
<p>正如你所看到的，和<code>VectorSource</code>一样，<code>VectorTileSource</code>也配置了<code>format</code>和<code>url</code>属性。MVT格式对象可以解析Mapbox矢量切片。与使用栅格数据切片一样，切片数据通过切片的缩放级别以及x和y坐标进行访问，因此，URL包含缩放级别的<code>&#123;z&#125;</code>占位符，以及瓦片坐标的<code>&#123;x&#125;</code>和<code>&#123;y&#125;</code>占位符。</p>
<p>运行项目代码后，一个未设置样式的矢量切片地图如下图所示：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vectortile/map.png" alt="一个未设置样式的世界地图"></p>
<details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vectortile/bright.html">https://openlayers.org/workshop/en/vectortile/bright.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vectortile/bright.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>显然，上面的地图需要设置样式，而<code>VectorTile</code>图层的样式与<code>Vector</code>图层的样式工作方式完全相同，因此在使用<code>Vector</code>图层时创建的样式也可以在这里使用。</p>
<p>对于像上面这样的地图，想为矢量图层创建数据驱动的样式其实很简单，但矢量切片也被用于街道地图，根据地图的缩放级别，样式通常会有很大差异，在这样的情况下，手工完成所有这些工作可能太耗时了。</p>
<p>在Web制图的历史上，曾有过许多尝试，创建了许多用于设置地图样式的工具和格式，这其中最流行的格式可能是SLD和CartoCSS，能想到的一个图形工具便是Atlas Styler，但这些格式或工具使用起来并不是非常方便。</p>
<p>Mapbox最终推出了Mapbox Studio，一个对用户非常友好的样式编辑器，以及Mapbox Style格式。Mapbox Style的格式易于手动读取和写入，并得到越来越多的应用程序的支持。图形化开源编辑器像Maputnik可作为Mapbox Studio的独立替代品，用于创建和修改Mapbox Style文件。</p>
<h3 id="使用MapBox-Style定义"><a href="#使用MapBox-Style定义" class="headerlink" title="使用MapBox Style定义"></a>使用MapBox Style定义</h3><p>在OpenLayers中使用带有Mapbox Style的矢量切片图层有两种方法，其中，最简单的是<code>MapboxVector</code>图层，它配置了一个指向Mapbox Style文档的url。首先导入需要用到的包：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">MapboxVectorLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/MapboxVector&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p>我们要使用的切片数据集位于 <a target="_blank" rel="noopener" href="https://cloud.maptiler.com/maps/bright/">https://cloud.maptiler.com/maps/bright/</a> ，要将其添加到我们的示例中，您将需要一个MapTiler帐户(请将下面代码中的key替换为你自己的)，或者，如果你有一个Mapbox帐户，你可以使用Mapbox中的地图原件(参见下面代码中的注释)。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">MapboxVectorLayer</span>(&#123;</span><br><span class="line">  <span class="attr">styleUrl</span>:</span><br><span class="line">    <span class="string">&#x27;https://api.maptiler.com/maps/bright/style.json?key=lirfd6Fegsjkvs0lshxe&#x27;</span>,</span><br><span class="line">  <span class="comment">// or, instead of the above, try</span></span><br><span class="line">  <span class="comment">// styleUrl: &#x27;mapbox://styles/mapbox/bright-v9&#x27;,</span></span><br><span class="line">  <span class="comment">// accessToken: &#x27;Your token from https://mapbox.com/&#x27;</span></span><br><span class="line">&#125;);</span><br><span class="line">map.<span class="title function_">addLayer</span>(layer);</span><br></pre></td></tr></table></figure>

<p>上面的代码替换了上一步中的<code>VectorTileLayer</code>。运行项目代码，打开地图后，放大到布宜诺斯艾利斯，效果如下：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vectortile/bright.png" alt="一张明亮的布宜诺斯艾利斯地图"></p>
<h3 id="根据MapBox-Style定义构建一幅完整的地图"><a href="#根据MapBox-Style定义构建一幅完整的地图" class="headerlink" title="根据MapBox Style定义构建一幅完整的地图"></a>根据MapBox Style定义构建一幅完整的地图</h3><p>Mapbox Style格式不仅仅是为样式化矢量数据而设计的，它是用来描述一个完整的地图，包括它所有的数据源和图层，以及它的初始视图配置(例如中心和缩放级别)。</p>
<p><a target="_blank" rel="noopener" href="https://npmjs.com/package/ol-mapbox-style/">ol-mapbox-style</a>包使得OpenLayers能够支持Mapbox Style格式。因此，使用OpenLayers矢量切片图层的第二种方法是：使用<code>ol-mapbox-style</code>来创建整个地图。修改main.js中的代码:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> olms <span class="keyword">from</span> <span class="string">&#x27;ol-mapbox-style&#x27;</span>;</span><br><span class="line"><span class="title function_">olms</span>(</span><br><span class="line">  <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;https://api.maptiler.com/maps/bright/style.json?key=lirfd6Fegsjkvs0lshxe&#x27;</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<h3 id="与矢量切片要素交互"><a href="#与矢量切片要素交互" class="headerlink" title="与矢量切片要素交互"></a>与矢量切片要素交互</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/vectortile/interact.html">https://openlayers.org/workshop/en/vectortile/interact.html</a></p>
<iframe src="https://openlayers.org/workshop/en/vectortile/interact.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>我们可以与加载到客户端的矢量切片数据的要素进行交互。有一点需要注意的是，矢量切片在渲染时被优化过，这意味着要素只包含过滤和渲染所需的属性，并且几何图形针对渲染的分辨率进行了优化，并在切片(瓦片)边界附近进行了裁剪。</p>
<p>在本次练习中，当鼠标悬停在要素上时，我们将在指针所在的位置周围画一个方框。</p>
<p>如下所示：导入需要用到的包：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">VectorLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/Vector&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VectorSource</span> <span class="keyword">from</span> <span class="string">&#x27;ol/source/Vector&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Stroke</span>, <span class="title class_">Style</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/style&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p>接下来，我们创建一个数据源并将其配置到矢量图层上:</p>
<blockquote>
<p>注：为矢量图层设置<code>map</code>属性的用处：将该层设置为地图上的overlay，地图不会在其Layers集合中管理该层，并且该层将在顶部渲染，这对于<strong>临时</strong>图层非常有用。而将图层添加到地图并由地图管理它的标准方法是使用<code>map.addLayer()</code>，详见api文档解释：<a target="_blank" rel="noopener" href="https://openlayers.org/en/latest/apidoc/module-ol_layer_Vector-VectorLayer.html">OpenLayers v7.3.0 API - Class: VectorLayer</a>。</p>
</blockquote>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> source = <span class="keyword">new</span> <span class="title class_">VectorSource</span>();</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">VectorLayer</span>(&#123;</span><br><span class="line">  <span class="attr">map</span>: map,</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">  <span class="attr">style</span>: <span class="keyword">new</span> <span class="title class_">Style</span>(&#123;</span><br><span class="line">    <span class="attr">stroke</span>: <span class="keyword">new</span> <span class="title class_">Stroke</span>(&#123;</span><br><span class="line">      <span class="attr">color</span>: <span class="string">&#x27;red&#x27;</span>,</span><br><span class="line">      <span class="attr">width</span>: <span class="number">4</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>现在是时候给地图添加一个<code>pointermove</code>监听器了，它可以获取指针位置上的所有要素，并将它们的边界框添加到图层中。我们需要两个额外的导入:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Feature</span> <span class="keyword">from</span> <span class="string">&#x27;ol/Feature&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;fromExtent&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/geom/Polygon&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p>最后，我们在获取新要素之前先清空数据源对象中的所有要素，并将指针位置的要素的边界框作为新要素添加到数据源对象中:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">map.<span class="title function_">on</span>(<span class="string">&#x27;pointermove&#x27;</span>, <span class="keyword">function</span> (<span class="params">event</span>) &#123;</span><br><span class="line">  source.<span class="title function_">clear</span>();</span><br><span class="line">  map.<span class="title function_">forEachFeatureAtPixel</span>(</span><br><span class="line">    event.<span class="property">pixel</span>,</span><br><span class="line">    <span class="keyword">function</span> (<span class="params">feature</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> geometry = feature.<span class="title function_">getGeometry</span>();</span><br><span class="line">      source.<span class="title function_">addFeature</span>(<span class="keyword">new</span> <span class="title class_">Feature</span>(<span class="title function_">fromExtent</span>(geometry.<span class="title function_">getExtent</span>())));</span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">hitTolerance</span>: <span class="number">2</span>,</span><br><span class="line">    &#125;</span><br><span class="line">  );</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>现在，当鼠标指针悬停在地图上时，结果应该如下所示：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/vectortile/interact.gif" alt="在地图上悬停鼠标指针"></p>
<h2 id="WebGL点的渲染"><a href="#WebGL点的渲染" class="headerlink" title="WebGL点的渲染"></a>WebGL点的渲染</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/webgl/">https://openlayers.org/workshop/en/webgl/</a></p>
<iframe src="https://openlayers.org/workshop/en/webgl/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在本节内容中，我们将使用WebGL从CSV文件中自定义渲染陨石撞击数据。</p>
<p>本文将借助Canvas 2D上下文并使用标准的矢量图层来渲染点要素，然后我们将使用新的点渲染工具来使用WebGL渲染相同的数据。最后，我们将开发一个自定义片段着色器来展示如何动态地设置数据样式。</p>
<h3 id="使用Canvas-2D渲染点"><a href="#使用Canvas-2D渲染点" class="headerlink" title="使用Canvas 2D渲染点"></a>使用Canvas 2D渲染点</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/webgl/meteorites.html">https://openlayers.org/workshop/en/webgl/meteorites.html</a></p>
<iframe src="https://openlayers.org/workshop/en/webgl/meteorites.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在OpenLayers 6版本中，地图中的每个图层都有一个独立的渲染器。这在以前，所有图层渲染都由单个地图渲染器来管理，而且所有图层的渲染使用的是单个渲染策略。因此，在OpenLayers 6版本之前，你需要去选择到底使用Canvas 2D去渲染所有图层还是使用WebGL去渲染所有图层。在OpenLayers 6中，您可以拥有由具有不同渲染策略的图层组成的地图。例如，可以使用Canvas 2D渲染器渲染一些图层，而使用WebGL渲染器渲染其他图层。</p>
<p><code>ol/layer/Vector</code>类会使用Canvas 2D来渲染点、线或多边形。该图层有一个功能齐全的渲染器，在要素的样式化(设计)方面具有很大的灵活性。对于大量的要素，WebGL是一种更合适的技术。在本文中，我们将首先使用Canvas 2D渲染45000个陨石位置，然后将示例迁移至使用WebGL来渲染。</p>
<p>首先，编辑index.html文件：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>OpenLayers<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="keyword">@import</span> <span class="string">&quot;node_modules/ol/ol.css&quot;</span>;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">html</span>, <span class="selector-tag">body</span>, <span class="selector-id">#map-container</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: sans-serif;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;map-container&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./main.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>接下来，我们将从本地CSV文件获取并解析数据，将生成的要素添加到矢量数据源，并使用矢量图层将其渲染到地图上。</p>
<p><code>meteorites.csv</code>文件中包含的数据如下所示：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">name,mass,year,reclat,reclong</span><br><span class="line">Aachen,21,1880,50.775000,6.083330</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>文件的第一行是标题行，我们将在解析时跳过它，之后的每一行根据首行的地点名称、陨石质量、撞击年份、纬度和经度使用逗号分隔。我们将使用<code>XMLHttpRequest</code>客户端来获取数据，并编写一个函数将文件中的每一行数据解析为具有点几何形状的要素。</p>
<p>更新<code>main.js</code>文件，以加载和渲染一个包含陨石撞击的数据的本地CSV文件:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Feature</span> <span class="keyword">from</span> <span class="string">&#x27;ol/Feature&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Point</span> <span class="keyword">from</span> <span class="string">&#x27;ol/geom/Point&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">TileLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/Tile&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">VectorLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/Vector&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Map</span>, <span class="title class_">View</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;ol&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;<span class="title class_">Stamen</span>, <span class="title class_">Vector</span> <span class="keyword">as</span> <span class="title class_">VectorSource</span>&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/source&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;fromLonLat&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/proj&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> source = <span class="keyword">new</span> <span class="title class_">VectorSource</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> client = <span class="keyword">new</span> <span class="title class_">XMLHttpRequest</span>();</span><br><span class="line">client.<span class="title function_">open</span>(<span class="string">&#x27;GET&#x27;</span>, <span class="string">&#x27;./data/meteorites.csv&#x27;</span>);</span><br><span class="line">client.<span class="property">onload</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> csv = client.<span class="property">responseText</span>;</span><br><span class="line">  <span class="keyword">const</span> features = [];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> prevIndex = csv.<span class="title function_">indexOf</span>(<span class="string">&#x27;\n&#x27;</span>) + <span class="number">1</span>; <span class="comment">// 忽略首行</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> curIndex;</span><br><span class="line">  <span class="keyword">while</span> ((curIndex = csv.<span class="title function_">indexOf</span>(<span class="string">&#x27;\n&#x27;</span>, prevIndex)) != -<span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> line = csv.<span class="title function_">substr</span>(prevIndex, curIndex - prevIndex).<span class="title function_">split</span>(<span class="string">&#x27;,&#x27;</span>);</span><br><span class="line">    prevIndex = curIndex + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> coords = <span class="title function_">fromLonLat</span>([<span class="built_in">parseFloat</span>(line[<span class="number">4</span>]), <span class="built_in">parseFloat</span>(line[<span class="number">3</span>])]);</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">isNaN</span>(coords[<span class="number">0</span>]) || <span class="built_in">isNaN</span>(coords[<span class="number">1</span>])) &#123;</span><br><span class="line">      <span class="comment">// guard against bad data，避免空(无效)数据的读取</span></span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    features.<span class="title function_">push</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">Feature</span>(&#123;</span><br><span class="line">        <span class="attr">mass</span>: <span class="built_in">parseFloat</span>(line[<span class="number">1</span>]) || <span class="number">0</span>,</span><br><span class="line">        <span class="attr">year</span>: <span class="built_in">parseInt</span>(line[<span class="number">2</span>]) || <span class="number">0</span>,</span><br><span class="line">        <span class="attr">geometry</span>: <span class="keyword">new</span> <span class="title class_">Point</span>(coords),</span><br><span class="line">      &#125;)</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">  source.<span class="title function_">addFeatures</span>(features);</span><br><span class="line">&#125;;</span><br><span class="line">client.<span class="title function_">send</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> meteorites = <span class="keyword">new</span> <span class="title class_">VectorLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">layers</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">      <span class="attr">source</span>: <span class="keyword">new</span> <span class="title class_">Stamen</span>(&#123;</span><br><span class="line">        <span class="attr">layer</span>: <span class="string">&#x27;toner&#x27;</span>,</span><br><span class="line">      &#125;),</span><br><span class="line">    &#125;),</span><br><span class="line">    meteorites,</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">view</span>: <span class="keyword">new</span> <span class="title class_">View</span>(&#123;</span><br><span class="line">    <span class="attr">center</span>: [<span class="number">0</span>, <span class="number">0</span>],</span><br><span class="line">    <span class="attr">zoom</span>: <span class="number">2</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>在获取和解析数据之后，特征被添加到矢量源。该源在平铺层上的矢量层中渲染。我们在这里没有对要素进行任何定制样式，重点只是想看看使用带有45,000个要素的地图是什么感觉，这些要素是用Canvas 2D渲染的。</p>
<p>在获取和解析数据之后，要素被添加到矢量数据源，该数据源会在已加载的切片图层的上方被渲染。在这里我们没有对要素配置任何的样式，重点只是想看看使用带有45,000个要素的地图是什么感觉，并且这些要素是用Canvas 2D渲染的。</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/webgl/meteorites.png" alt="陨石撞击地点"></p>
<h3 id="使用WebGL渲染点"><a href="#使用WebGL渲染点" class="headerlink" title="使用WebGL渲染点"></a>使用WebGL渲染点</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/webgl/points.html">https://openlayers.org/workshop/en/webgl/points.html</a></p>
<iframe src="https://openlayers.org/workshop/en/webgl/points.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在上一小节中，我们使用标准的矢量图层来渲染点要素，并且该图层使用Canvas 2D上下文进行了渲染。有了这个图层，只需认真编写高效的样式代码，就可以渲染数万个点。但对于渲染更多的点，或者进行更高效的动态样式，WebGL会是一个更好的解决方案。OpenLayers拥有越来越多的使用WebGL进行渲染的实用程序集。在本练习中，我们将使用它来渲染点的几何图形。</p>
<p>首先，我们将导入启用webgl的点图层的构造函数，这种图层是利用WebGL技术优势的一个易于使用的入口点。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">WebGLPointsLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/WebGLPoints&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p>现在可以将之前导入的<code>VectorLayer</code>包删除。</p>
<p>替换康斯特陨石。使用与上一小节相同的矢量数据源，对<code>WebGLPointsLayer</code>的实例进行赋值。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> meteorites = <span class="keyword">new</span> <span class="title class_">WebGLPointsLayer</span>(&#123;</span><br><span class="line">  <span class="attr">source</span>: source,</span><br><span class="line">  <span class="attr">style</span>: &#123;</span><br><span class="line">    <span class="attr">symbol</span>: &#123;</span><br><span class="line">      <span class="attr">symbolType</span>: <span class="string">&#x27;circle&#x27;</span>,</span><br><span class="line">      <span class="attr">size</span>: <span class="number">14</span>,</span><br><span class="line">      <span class="attr">color</span>: <span class="string">&#x27;rgb(255, 0, 0)&#x27;</span>,</span><br><span class="line">      <span class="attr">opacity</span>: <span class="number">0.5</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>运行项目代码，用WebGL渲染的陨石撞击地点的效果如下：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/webgl/circles.png" alt="以圆形渲染的影响地点"></p>
<p>从上面的代码可以看到，我们在创建图层时指定了<code>style</code>参数，并且该样式允许我们指定点的外观(红色、半透明圆)。</p>
<p>更改WebGL图层的样式与Openlayers库的其余部分非常不同。我们不像其他矢量图层那样使用<code>Fill</code>、<code>Stroke</code>和<code>Image</code>类，而只需为对象提供<code>Style</code>参数。该对象支持的属性则直截了当：<code>opacity</code>(透明度)、<code>color</code>(颜色)、<code>size</code>(大小)、<code>offset</code>(偏移量)、<code>src</code>(用于图像)和<code>symbolType</code>(可以是<code>circle</code>(圆形)、<code>square</code>(正方形)、<code>triangle</code>(三角形)或<code>image</code>(图像))。</p>
<blockquote>
<p>WebGL图层使用完全不同的渲染系统，style对象实际上被动态转换为片段和顶点着色器。</p>
</blockquote>
<p>通过在地图中导航，你会发现与使用标准Canvas 2D渲染的图层相比，性能有所提高。</p>
<p>现在，事实上这张地图看起来并不是很好看：因为每个点都有相同的样式。</p>
<p>让我们先根据<strong>陨石的质量</strong>来确定圆的大小，为了实现这一点，我们将样式的大小替换为以下表达式:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">size</span>: [</span><br><span class="line">  <span class="string">&#x27;+&#x27;</span>,</span><br><span class="line">  [<span class="string">&#x27;*&#x27;</span>, [<span class="string">&#x27;clamp&#x27;</span>, [<span class="string">&#x27;*&#x27;</span>, [<span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;mass&#x27;</span>], <span class="number">1</span> / <span class="number">20000</span>], <span class="number">0</span>, <span class="number">1</span>], <span class="number">18</span>],</span><br><span class="line">  <span class="number">8</span>,</span><br><span class="line">],</span><br></pre></td></tr></table></figure>

<p>这个表达式会产生最小8个像素的大小，它可以根据陨石的质量增加18个像素，而且<code>WebGLPointsLayer</code>类支持采用类似上述的这种表达式，配置其样式的数值属性(如<code>size</code>(大小)、<code>opacity</code>(透明度)、<code>color components</code>(颜色组件)等)。</p>
<p>表达式由包含运算符的数组来表示，如下所示:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[<span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;mass&#x27;</span>]</span><br><span class="line">[<span class="string">&#x27;clamp&#x27;</span>, value, <span class="number">0</span>, <span class="number">1</span>]</span><br><span class="line">[<span class="string">&#x27;*&#x27;</span>, value, <span class="number">18</span>]</span><br><span class="line">[<span class="string">&#x27;+&#x27;</span>, value, <span class="number">8</span>]</span><br></pre></td></tr></table></figure>

<p>第一个操作符<code>get</code>将根据要素的名称读取要素的属性。这里展示的其他操作符<code>clamp</code>、<code>*</code>和<code>+</code>允许操作另一个运算符的输出。在前面的例子中，我们用这些数据将陨石的质量数值转换为8到26之间，并作为数值最终的大小。</p>
<p>实现的效果如下：看起来好看多了。</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/webgl/dynamic.png" alt="以陨石质量为大小的圆圈"></p>
<h3 id="制作陨石撞击动画"><a href="#制作陨石撞击动画" class="headerlink" title="制作陨石撞击动画"></a>制作陨石撞击动画</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/webgl/animated.html">https://openlayers.org/workshop/en/webgl/animated.html</a></p>
<iframe src="https://openlayers.org/workshop/en/webgl/animated.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>到目前为止，我们已经设法从CSV文件中获得数据，并使用WebGL进行了渲染，但地图展示的效果看起来依旧不是很好。我们使用陨石的质量来确定圆的半径，但我们没有使用陨石撞击日期，我们需要在点要素中将其解析为<code>year</code>属性。</p>
<p>我们陨石撞击数据的<code>year</code>属性的值从1850到2015年不等。我们将设置一个动画循环，该循环会递增当前年份，在陨石的影响的年份渲染陨石，然后随着时间的推移减小其大小和不透明度。</p>
<p>第一步，我们将开始执行动画循环，并将当前年份呈现为地图顶部的<code>&lt;div&gt;</code>，在<code>index.html</code>中的地图容器(即<code>&lt;div id=&quot;map-container&quot;&gt;&lt;/div&gt;</code>)后面添加以下内容：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;year&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>编辑<code>&lt;style&gt;</code>样式标签，添加如下样式：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#year</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">bottom</span>: <span class="number">1em</span>;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">1em</span>;</span><br><span class="line">  <span class="attribute">color</span>: white;</span><br><span class="line">  -webkit-text-stroke: <span class="number">1px</span> black;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">2em</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>: bold;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>现在，打开<code>main.js</code>文件，我们将声明一些变量来表示数据的时间范围和动画进行的速度,在<code>main.js</code>添加以下代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> minYear = <span class="number">1850</span>;</span><br><span class="line"><span class="keyword">const</span> maxYear = <span class="number">2015</span>;</span><br><span class="line"><span class="keyword">const</span> span = maxYear - minYear;</span><br><span class="line"><span class="keyword">const</span> rate = <span class="number">10</span>; <span class="comment">// years per second</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> start = <span class="title class_">Date</span>.<span class="title function_">now</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> styleVariables = &#123;</span><br><span class="line">  <span class="attr">currentYear</span>: minYear,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>为了能够在样式表达式中访问当前年份，我们需要为图层的<code>style</code>对象上的<code>variables</code>属性配置为<code>styleVariables</code>：<code>style</code>对象中的<code>variables</code>是可用于计算的表达式中的数值。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">variables</span>: styleVariables,</span><br></pre></td></tr></table></figure>

<p>接下来，我们需要将Map的实例分配给一个可以稍后引用的<code>map</code>变量:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> map = <span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br></pre></td></tr></table></figure>

<p>在地图配置下面，添加以下<code>render</code>函数来启动动画循环。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> yearElement = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;year&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> elapsed = (rate * (<span class="title class_">Date</span>.<span class="title function_">now</span>() - start)) / <span class="number">1000</span>;</span><br><span class="line">  styleVariables.<span class="property">currentYear</span> = <span class="title class_">Math</span>.<span class="title function_">round</span>(minYear + (elapsed % span));</span><br><span class="line">  yearElement.<span class="property">innerText</span> = styleVariables.<span class="property">currentYear</span>;</span><br><span class="line"></span><br><span class="line">  map.<span class="title function_">render</span>();</span><br><span class="line">  <span class="title function_">requestAnimationFrame</span>(render);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">render</span>();</span><br></pre></td></tr></table></figure>

<p>如果你做对了上述这些工作，你应该会在地图的左下角看到年份的流逝。</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/webgl/years.gif" alt="年份的流逝"></p>
<p>我们还向<code>meteorites</code>图层添加了另一个图层选项，这将提高动画的性能，这样便不需要在每个动画步骤中处理命中检测数据。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">disableHitDetection</span>: <span class="literal">true</span>,</span><br></pre></td></tr></table></figure>

<p>现在为了在地图上显示时间背景，我们只想从陨石撞击的时间开始，每颗陨石将显式10年的时间。为此，我们将向<code>meteorites</code>图层的<code>style</code>对象添加<code>filter</code>属性：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">filter</span>: [<span class="string">&#x27;between&#x27;</span>, [<span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;year&#x27;</span>], periodStart, [<span class="string">&#x27;var&#x27;</span>, <span class="string">&#x27;currentYear&#x27;</span>]],</span><br></pre></td></tr></table></figure>

<p>这个过滤器引用了一个<code>periodStart</code>变量，它本身就是一个表达式，我们需要定义它。在这里，我们定义了一些更多的样式对象，让我们在<code>const meteorites</code>图层定义中添加以下内容:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> period = <span class="number">10</span>;</span><br><span class="line"><span class="keyword">const</span> periodStart = [<span class="string">&#x27;-&#x27;</span>, [<span class="string">&#x27;var&#x27;</span>, <span class="string">&#x27;currentYear&#x27;</span>], period];</span><br><span class="line"><span class="keyword">const</span> decay = [</span><br><span class="line">  <span class="string">&#x27;interpolate&#x27;</span>,</span><br><span class="line">  [<span class="string">&#x27;linear&#x27;</span>],</span><br><span class="line">  [<span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;year&#x27;</span>],</span><br><span class="line">  periodStart,</span><br><span class="line">  <span class="number">0</span>,</span><br><span class="line">  [<span class="string">&#x27;var&#x27;</span>, <span class="string">&#x27;currentYear&#x27;</span>],</span><br><span class="line">  <span class="number">1</span>,</span><br><span class="line">];</span><br></pre></td></tr></table></figure>

<p><code>decay</code>是一个表达式，我们将使用它来减小圆圈的大小和不透明度，以使这些圆圈在地图上逐渐淡出。<code>decay</code>为我们提供了一个0到1之间的值，我们可以将其应用为褪色效果的乘数，若要使用它随着时间减小圆圈的大小，我们必须修改<code>style</code>对象中的<code>size</code>属性:下面新的<code>size</code>表达式的第四行就是之前的表达式。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">size</span>: [</span><br><span class="line">  <span class="string">&#x27;*&#x27;</span>,</span><br><span class="line">  decay,</span><br><span class="line">  [<span class="string">&#x27;+&#x27;</span>, [<span class="string">&#x27;*&#x27;</span>, [<span class="string">&#x27;clamp&#x27;</span>, [<span class="string">&#x27;*&#x27;</span>, [<span class="string">&#x27;get&#x27;</span>, <span class="string">&#x27;mass&#x27;</span>], <span class="number">1</span> / <span class="number">20000</span>], <span class="number">0</span>, <span class="number">1</span>], <span class="number">18</span>], <span class="number">8</span>],</span><br><span class="line">],</span><br></pre></td></tr></table></figure>

<p>同样地，为了随着时间的推移减少不透明度，我们还将<code>decay</code>应用于<code>opacity</code>属性：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">opacity</span>: [<span class="string">&#x27;*&#x27;</span>, <span class="number">0.5</span>, decay],</span><br></pre></td></tr></table></figure>

<p>最终实现的效果如下：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/webgl/shower.gif" alt="流星雨的效果"></p>
<p>大功告成！</p>
<h2 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/deploying/">https://openlayers.org/workshop/en/deploying/</a></p>
<iframe src="https://openlayers.org/workshop/en/deploying/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在整个研讨会中，我们一直使用开发服务器来查看示例。这类似于使用<a target="_blank" rel="noopener" href="https://www.npmjs.com/package/ol">ol包</a>开发应用程序时使用的设置。当你准备好部署应用程序时，你将希望使用构建步骤创建应用程序入口点的精简捆绑包。</p>
<p>在开发期间，我们一直在使用<a target="_blank" rel="noopener" href="https://vitejs.dev/">Vite</a>进行模块捆绑。当我们使用<code>npm start</code>启动开发服务器时，我们在<code>development</code>模式下运行Vite。在<code>production</code>模式下，捆绑包被压缩。</p>
<p>要构建用于部署的资源，我们将运行<code>package.json</code>中的<code>build</code>脚本：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm run build</span><br></pre></td></tr></table></figure>

<p>这将运行<code>vite build</code>，它会将<code>data</code>目录复制到<code>dist/</code>文件夹。</p>
<p>构建完成后，<code>dist</code>目录中将会有打包后的资源，这些资源便是你要部署到生产服务器(如S3，或使你希望托管应用程序的任何位置)的资源。你可以通过运行本地的http服务器来查看应用程序的样子，命令如下所示：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx serve dist</span><br></pre></td></tr></table></figure>

<p>现在可以打开<code>http://localhost:3000/</code>查看应用程序在生产环境中的效果。</p>
<p>就是这样，你已经完成了！</p>
<h2 id="数据切片"><a href="#数据切片" class="headerlink" title="数据切片"></a>数据切片</h2><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/data-tiles/">https://openlayers.org/workshop/en/data-tiles/</a></p>
<iframe src="https://openlayers.org/workshop/en/data-tiles/" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>
<p>WebGL切片图层渲染器允许我们在渲染之前处理像素值。在接下来的练习中，我们会将高程数据编码为RGB值的切片集合。在这一部分中，我们还将创建一张地图，让用户可以控制海平面，使用滑块控件来改变的海平面数值，我们将在底图上呈现用户调整后的海平面。</p>
<h3 id="创建地图"><a href="#创建地图" class="headerlink" title="创建地图"></a>创建地图</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/data-tiles/map.html">https://openlayers.org/workshop/en/data-tiles/map.html</a></p>
<iframe src="https://openlayers.org/workshop/en/data-tiles/map.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>编辑index.html文件，以便开始在整个页面渲染一幅地图：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;utf-8&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>OpenLayers<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="keyword">@import</span> <span class="string">&quot;node_modules/ol/ol.css&quot;</span>;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">style</span>&gt;</span><span class="language-css"></span></span><br><span class="line"><span class="language-css">      <span class="selector-tag">html</span>, <span class="selector-tag">body</span>, <span class="selector-id">#map-container</span> &#123;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">margin</span>: <span class="number">0</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">height</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">width</span>: <span class="number">100%</span>;</span></span><br><span class="line"><span class="language-css">        <span class="attribute">font-family</span>: sans-serif;</span></span><br><span class="line"><span class="language-css">      &#125;</span></span><br><span class="line"><span class="language-css">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;map-container&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;./main.js&quot;</span> <span class="attr">type</span>=<span class="string">&quot;module&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>清除main.js并添加以下导入：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Map</span> <span class="keyword">from</span> <span class="string">&#x27;ol/Map.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">TileLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/WebGLTile.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">View</span> <span class="keyword">from</span> <span class="string">&#x27;ol/View.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="variable constant_">XYZ</span> <span class="keyword">from</span> <span class="string">&#x27;ol/source/XYZ.js&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;fromLonLat&#125; <span class="keyword">from</span> <span class="string">&#x27;ol/proj.js&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p>在本章节中，我们将使用<a target="_blank" rel="noopener" href="https://www.maptiler.com/cloud/">MapTeller</a>中的切片数据。如果你还没有账户，你可以注册一个免费的账户，并在这些例子中使用你的密钥。注册账号后，找到你的<a target="_blank" rel="noopener" href="https://cloud.maptiler.com/account/keys/">默认API密钥</a>，并将其添加到<code>main.js</code>中：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> key = <span class="string">&#x27;&lt;your-default-api-key&gt;&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p>为了验证一切是否正常工作，我们将使用你的API密钥创建一幅具有单个图层的地图。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> attributions =</span><br><span class="line">  <span class="string">&#x27;&lt;a href=&quot;https://www.maptiler.com/copyright/&quot; target=&quot;_blank&quot;&gt;&amp;copy; MapTiler&lt;/a&gt; &#x27;</span> +</span><br><span class="line">  <span class="string">&#x27;&lt;a href=&quot;https://www.openstreetmap.org/copyright&quot; target=&quot;_blank&quot;&gt;&amp;copy; OpenStreetMap contributors&lt;/a&gt;&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">Map</span>(&#123;</span><br><span class="line">  <span class="attr">target</span>: <span class="string">&#x27;map-container&#x27;</span>,</span><br><span class="line">  <span class="attr">layers</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">      <span class="attr">source</span>: <span class="keyword">new</span> <span class="title function_">XYZ</span>(&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">&#x27;https://api.maptiler.com/maps/streets/&#123;z&#125;/&#123;x&#125;/&#123;y&#125;.png?key=&#x27;</span> + key,</span><br><span class="line">        <span class="attr">attributions</span>: attributions,</span><br><span class="line">        <span class="attr">crossOrigin</span>: <span class="string">&#x27;anonymous&#x27;</span>,</span><br><span class="line">        <span class="attr">tileSize</span>: <span class="number">512</span>,</span><br><span class="line">      &#125;),</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">view</span>: <span class="keyword">new</span> <span class="title class_">View</span>(&#123;</span><br><span class="line">    <span class="attr">center</span>: <span class="title function_">fromLonLat</span>([-<span class="number">58.3816</span>, -<span class="number">34.6037</span>]),</span><br><span class="line">    <span class="attr">zoom</span>: <span class="number">11</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>效果如下：</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/data-tiles/map.png" alt="一幅显示布宜诺斯艾利斯的地图"></p>
<h3 id="渲染高程数据"><a href="#渲染高程数据" class="headerlink" title="渲染高程数据"></a>渲染高程数据</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/data-tiles/elevation.html">https://openlayers.org/workshop/en/data-tiles/elevation.html</a></p>
<iframe src="https://openlayers.org/workshop/en/data-tiles/elevation.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>MapTiler提供了一个全球的切片数据集，其中的<a target="_blank" rel="noopener" href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/tiles/terrain-rgb/">高程数据</a>被编码为PNG文件。</p>
<p>让我们将这些切片添加到地图上。在<code>main.js</code>中，创建一个使用MapTiler<code>terrain-rgb</code>切片数据集的新图层：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">  <span class="attr">opacity</span>: <span class="number">0.6</span>,</span><br><span class="line">  <span class="attr">source</span>: <span class="keyword">new</span> <span class="title function_">XYZ</span>(&#123;</span><br><span class="line">    <span class="attr">url</span>:</span><br><span class="line">      <span class="string">&#x27;https://api.maptiler.com/tiles/terrain-rgb/&#123;z&#125;/&#123;x&#125;/&#123;y&#125;.png?key=&#x27;</span> + key,</span><br><span class="line">    <span class="attr">maxZoom</span>: <span class="number">10</span>,</span><br><span class="line">    <span class="attr">tileSize</span>: <span class="number">512</span>,</span><br><span class="line">    <span class="attr">crossOrigin</span>: <span class="string">&#x27;anonymous&#x27;</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>将该图层添加到地图后，通过重新加载页面，在你的基础图层上会显示一些奇怪的颜色切片数据。Terrain-RGB切片中的高程数据会通过rgb通道进行编码。因此，虽然这些数据并不是直接被渲染，但它看起来很有趣。</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/data-tiles/elevation.png" alt="Terrain-RGB切片在地图上的渲染效果"></p>
<h3 id="渲染海平面"><a href="#渲染海平面" class="headerlink" title="渲染海平面"></a>渲染海平面</h3><details class="toggle" style="border: 1px solid black"><summary class="toggle-button" style="background-color: black;color: #fff">本小节原文链接及内容</summary><div class="toggle-content"><p>原文链接：<a target="_blank" rel="noopener" href="https://openlayers.org/workshop/en/data-tiles/sea-level.html">https://openlayers.org/workshop/en/data-tiles/sea-level.html</a></p>
<iframe src="https://openlayers.org/workshop/en/data-tiles/sea-level.html" width="100%" height="400px" frameborder="0" seamless></iframe></div></details>

<p>在上节小节中，我们直接在地图上渲染了Terrain-RGB切片数据，接下来我们要做的是在地图上渲染海平面，我们希望用户能够调整海平面以上的高度，并在地图上看到调整后的高度。为此，我们将使用WebGL切片图层直接处理高程数据，并通过在页面上的拖动滑块控件获取用户的输入。</p>
<p>让我们先将控件添加到页面，打开在<code>index.html</code>，添加以下标签和滑块控件：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">label</span> <span class="attr">id</span>=<span class="string">&quot;slider&quot;</span>&gt;</span></span><br><span class="line">  Sea level</span><br><span class="line">  <span class="tag">&lt;<span class="name">input</span> <span class="attr">id</span>=<span class="string">&quot;level&quot;</span> <span class="attr">type</span>=<span class="string">&quot;range&quot;</span> <span class="attr">min</span>=<span class="string">&quot;0&quot;</span> <span class="attr">max</span>=<span class="string">&quot;100&quot;</span> <span class="attr">value</span>=<span class="string">&quot;1&quot;</span>/&gt;</span></span><br><span class="line">  +<span class="tag">&lt;<span class="name">span</span> <span class="attr">id</span>=<span class="string">&quot;output&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">span</span>&gt;</span> m</span><br><span class="line"><span class="tag">&lt;/<span class="name">label</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>然后给这些控件添加一些样式(在<code>index.html</code>的<code>&lt;style&gt;</code>中)：</p>
<figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-id">#slider</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">bottom</span>: <span class="number">1rem</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">  <span class="attribute">text-align</span>: center;</span><br><span class="line">  <span class="attribute">text-shadow</span>: <span class="number">0px</span> <span class="number">0px</span> <span class="number">4px</span> <span class="built_in">rgba</span>(<span class="number">255</span>, <span class="number">255</span>, <span class="number">255</span>, <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们希望在渲染之前操作像素值，而不是直接渲染从Terrain-RGB切片数据获取的R、G、B、A值。WebGL切片图层会在渲染样式表达式之前处理它们中的像素值，此表达式会针对输入源中的每个像素进行求值。</p>
<p>首先，导入<code>WebGLTile</code>图层的类(在<code>main.js</code>中)：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">TileLayer</span> <span class="keyword">from</span> <span class="string">&#x27;ol/layer/WebGLTile.js&#x27;</span>;</span><br></pre></td></tr></table></figure>

<p><code>terrain-rgb</code>切片数据中的高程值会根据<a target="_blank" rel="noopener" href="https://cloud.maptiler.com/auth/widget?next=https://cloud.maptiler.com/tiles/terrain-rgb/">以下公式</a>进行编码，并且会被编码为的红色、绿色和蓝色值(从0到255)：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">elevation = -<span class="number">10000</span> + ((R * <span class="number">256</span> * <span class="number">256</span> + G * <span class="number">256</span> + B) * <span class="number">0.1</span>)</span><br></pre></td></tr></table></figure>

<p>我们可以使用WebGL风格的表达式语言来表示该公式，将下面的表达式添加到<code>main.js</code>中。此表达式会对输入的高程数据进行解码，即将红色、绿色和蓝色值转换为单个高程测量。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// band math operates on normalized values from 0-1</span></span><br><span class="line"><span class="comment">// so we scale by 255 to align with the elevation formula</span></span><br><span class="line"><span class="comment">// from https://cloud.maptiler.com/tiles/terrain-rgb/</span></span><br><span class="line"><span class="keyword">const</span> elevation = [</span><br><span class="line">  <span class="string">&#x27;+&#x27;</span>,</span><br><span class="line">  -<span class="number">10000</span>,</span><br><span class="line">  [</span><br><span class="line">    <span class="string">&#x27;*&#x27;</span>,</span><br><span class="line">    <span class="number">0.1</span> * <span class="number">255</span>,</span><br><span class="line">    [</span><br><span class="line">      <span class="string">&#x27;+&#x27;</span>,</span><br><span class="line">      [<span class="string">&#x27;*&#x27;</span>, <span class="number">256</span> * <span class="number">256</span>, [<span class="string">&#x27;band&#x27;</span>, <span class="number">1</span>]],</span><br><span class="line">      [<span class="string">&#x27;+&#x27;</span>, [<span class="string">&#x27;*&#x27;</span>, <span class="number">256</span>, [<span class="string">&#x27;band&#x27;</span>, <span class="number">2</span>]], [<span class="string">&#x27;band&#x27;</span>, <span class="number">3</span>]],</span><br><span class="line">    ],</span><br><span class="line">  ],</span><br><span class="line">];</span><br></pre></td></tr></table></figure>

<p>使用上面的表达式创建一个WebGL切片图层:</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> layer = <span class="keyword">new</span> <span class="title class_">TileLayer</span>(&#123;</span><br><span class="line">  <span class="attr">opacity</span>: <span class="number">0.6</span>,</span><br><span class="line">  <span class="attr">source</span>: <span class="keyword">new</span> <span class="title function_">XYZ</span>(&#123;</span><br><span class="line">    <span class="attr">url</span>:</span><br><span class="line">      <span class="string">&#x27;https://api.maptiler.com/tiles/terrain-rgb/&#123;z&#125;/&#123;x&#125;/&#123;y&#125;.png?key=&#x27;</span> + key,</span><br><span class="line">    <span class="attr">maxZoom</span>: <span class="number">10</span>,</span><br><span class="line">    <span class="attr">tileSize</span>: <span class="number">512</span>,</span><br><span class="line">    <span class="attr">crossOrigin</span>: <span class="string">&#x27;anonymous&#x27;</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">  <span class="attr">style</span>: &#123;</span><br><span class="line">    <span class="attr">variables</span>: &#123;</span><br><span class="line">      <span class="attr">level</span>: <span class="number">0</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">color</span>: [</span><br><span class="line">      <span class="string">&#x27;case&#x27;</span>,</span><br><span class="line">      [<span class="string">&#x27;&lt;=&#x27;</span>, elevation, [<span class="string">&#x27;var&#x27;</span>, <span class="string">&#x27;level&#x27;</span>]],</span><br><span class="line">      [<span class="number">139</span>, <span class="number">212</span>, <span class="number">255</span>, <span class="number">1</span>],</span><br><span class="line">      [<span class="number">139</span>, <span class="number">212</span>, <span class="number">255</span>, <span class="number">0</span>],</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>上面的<code>color</code>表达式使用<code>case</code>表达式将海平面或海平面以下的值着色为蓝色，而高于海平面的高程值将是透明的(alpha值为<code>0</code>)。</p>
<p>接下来，我们需要监听滑块控件上值的更改，并在用户调整该值时更新<code>seaLevel</code>样式变量。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> control = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;level&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> output = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;output&#x27;</span>);</span><br><span class="line"><span class="keyword">const</span> listener = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  output.<span class="property">innerText</span> = control.<span class="property">value</span>;</span><br><span class="line">  layer.<span class="title function_">updateStyleVariables</span>(&#123;<span class="attr">level</span>: <span class="built_in">parseFloat</span>(control.<span class="property">value</span>)&#125;);</span><br><span class="line">&#125;;</span><br><span class="line">control.<span class="title function_">addEventListener</span>(<span class="string">&#x27;input&#x27;</span>, listener);</span><br><span class="line">control.<span class="title function_">addEventListener</span>(<span class="string">&#x27;change&#x27;</span>, listener);</span><br><span class="line">output.<span class="property">innerText</span> = control.<span class="property">value</span>;</span><br></pre></td></tr></table></figure>

<p>一切都准备就绪，现在地图上应该有一个滑块，用户可以通过拖动滑块来控制海平面的变化。</p>
<p><img src= "/img/friend_404.gif" data-lazy-src="https://openlayers.org/workshop/en/data-tiles/sea-level.png" alt="海平面上升"></p>
</div><hr/><div id="post-comment"><div class="comment-head"><div class="comment-headline"><i class="fas fa-comments fa-fw"></i><span> 评论</span></div></div><div class="comment-wrap"><div><div class="vcomment" id="vcomment"></div></div></div></div></div><div class="aside-content" id="aside-content"><div class="card-widget card-info"><div class="is-center"><div class="avatar-img"><img src= "/img/friend_404.gif" data-lazy-src="/img/avatar.png" onerror="this.onerror=null;this.src='/img/friend_404.gif'" alt="avatar"/></div><div class="author-info__name">lovewhoilove</div><div class="author-info__description">生活原本沉闷，但跑起来就有风</div></div><div class="card-info-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">250</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">327</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">2</div></a></div></div><div class="card-widget card-announcement"><div class="item-headline"><i class="fas fa-bullhorn fa-shake"></i><span>公告</span></div><div class="announcement_content">陆陆续续尝试翻译Openlayers API文档，并尝试用实例来深入学习。</div></div><div class="card-widget tzy-right-widget" id="card-wechat"><div id="flip-wrapper"><div id="flip-content"><div class="face"></div><div class="back face"></div></div></div></div><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%BC%95%E8%A8%80"><span class="toc-number">1.</span> <span class="toc-text">引言</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE"><span class="toc-number">1.1.</span> <span class="toc-text">开发环境配置</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A6%82%E8%BF%B0"><span class="toc-number">1.2.</span> <span class="toc-text">概述</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5"><span class="toc-number">2.</span> <span class="toc-text">基础概念</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%95%8C%E9%9D%A2%E7%9A%84%E7%BC%96%E5%86%99"><span class="toc-number">2.1.</span> <span class="toc-text">界面的编写</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%A8%8B%E5%BA%8F%E7%9A%84%E5%85%A5%E5%8F%A3%E6%96%87%E4%BB%B6"><span class="toc-number">2.2.</span> <span class="toc-text">程序的入口文件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%9F%A5%E7%9C%8B%E5%9C%B0%E5%9B%BE"><span class="toc-number">2.3.</span> <span class="toc-text">查看地图</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%89%A9%E5%B1%95%E9%98%85%E8%AF%BB"><span class="toc-number">2.4.</span> <span class="toc-text">扩展阅读</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%9F%A2%E9%87%8F%E6%95%B0%E6%8D%AE"><span class="toc-number">3.</span> <span class="toc-text">矢量数据</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B8%B2%E6%9F%93GeoJSON"><span class="toc-number">3.1.</span> <span class="toc-text">渲染GeoJSON</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%8B%96%E6%94%BE%E4%BA%A4%E4%BA%92"><span class="toc-number">3.2.</span> <span class="toc-text">拖放交互</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BF%AE%E6%94%B9%E8%A6%81%E7%B4%A0"><span class="toc-number">3.3.</span> <span class="toc-text">修改要素</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%BB%98%E5%88%B6%E6%96%B0%E8%A6%81%E7%B4%A0"><span class="toc-number">3.4.</span> <span class="toc-text">绘制新要素</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%80%E5%90%AF%E6%8D%95%E6%8D%89%E4%BA%A4%E4%BA%92"><span class="toc-number">3.5.</span> <span class="toc-text">开启捕捉交互</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%8B%E8%BD%BD%E8%A6%81%E7%B4%A0"><span class="toc-number">3.6.</span> <span class="toc-text">下载要素</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%AE%A9%E5%8A%A0%E8%BD%BD%E7%9A%84%E6%95%B0%E6%8D%AE%E7%9C%8B%E8%B5%B7%E6%9D%A5%E6%9B%B4%E6%BC%82%E4%BA%AE"><span class="toc-number">3.7.</span> <span class="toc-text">让加载的数据看起来更漂亮</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E9%9D%99%E6%80%81%E6%A0%B7%E5%BC%8F"><span class="toc-number">3.7.1.</span> <span class="toc-text">静态样式</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%8A%A8%E6%80%81%E6%A0%B7%E5%BC%8F"><span class="toc-number">3.7.2.</span> <span class="toc-text">动态样式</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%A0%B9%E6%8D%AE%E5%87%A0%E4%BD%95%E5%9B%BE%E5%BD%A2%E7%9A%84%E9%9D%A2%E7%A7%AF%E6%9D%A5%E8%AE%BE%E7%BD%AE%E6%A0%B7%E5%BC%8F"><span class="toc-number">3.7.3.</span> <span class="toc-text">根据几何图形的面积来设置样式</span></a></li></ol></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%A7%BB%E5%8A%A8%E5%9C%B0%E5%9B%BE%E5%92%8C%E4%BC%A0%E6%84%9F%E5%99%A8"><span class="toc-number">4.</span> <span class="toc-text">移动地图和传感器</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%A7%BB%E5%8A%A8%E5%9C%B0%E5%9B%BE"><span class="toc-number">4.1.</span> <span class="toc-text">移动地图</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%B8%BA%E7%A7%BB%E5%8A%A8%E7%AB%AF%E7%BD%91%E9%A1%B5%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AAmeta%E6%A0%87%E7%AD%BE"><span class="toc-number">4.1.1.</span> <span class="toc-text">为移动端网页添加一个meta标签</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E6%B7%BB%E5%8A%A0%E5%AF%BC%E8%88%AA%E7%94%A8%E7%9A%84%E8%A1%97%E9%81%93%E5%9C%B0%E5%9B%BE"><span class="toc-number">4.1.2.</span> <span class="toc-text">添加导航用的街道地图</span></a></li><li class="toc-item toc-level-4"><a class="toc-link" href="#%E5%9C%A8%E7%A7%BB%E5%8A%A8%E8%AE%BE%E5%A4%87%E4%B8%8A%E6%B5%8B%E8%AF%95"><span class="toc-number">4.1.3.</span> <span class="toc-text">在移动设备上测试</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%9C%B0%E7%90%86%E4%BD%8D%E7%BD%AE"><span class="toc-number">4.2.</span> <span class="toc-text">地理位置</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%8C%87%E5%8D%97%E9%92%88"><span class="toc-number">4.3.</span> <span class="toc-text">指南针</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#GeoTIFF%E6%B8%B2%E6%9F%93"><span class="toc-number">5.</span> <span class="toc-text">GeoTIFF渲染</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9C%9F%E5%BD%A9%E8%89%B2GeoTIFF"><span class="toc-number">5.1.</span> <span class="toc-text">真彩色GeoTIFF</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%AE%80%E5%8C%96%E5%9C%B0%E5%9B%BE%E8%A7%86%E5%9B%BE%E7%9A%84%E9%85%8D%E7%BD%AE"><span class="toc-number">5.2.</span> <span class="toc-text">简化地图视图的配置</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%81%87%E5%BD%A9%E8%89%B2%E5%90%88%E6%88%90"><span class="toc-number">5.3.</span> <span class="toc-text">假彩色合成</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B3%A2%E6%AE%B5%E8%AE%A1%E7%AE%97"><span class="toc-number">5.4.</span> <span class="toc-text">波段计算</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#colormap%E7%A8%8B%E5%BA%8F%E5%8C%85"><span class="toc-number">5.5.</span> <span class="toc-text">colormap程序包</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B7%BB%E5%8A%A0%E4%B8%8B%E6%8B%89%E6%A1%86"><span class="toc-number">5.6.</span> <span class="toc-text">添加下拉框</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%9F%A2%E9%87%8F%E5%88%87%E7%89%87%E5%92%8CMapbox%E6%A0%B7%E5%BC%8F"><span class="toc-number">6.</span> <span class="toc-text">矢量切片和Mapbox样式</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%9F%A2%E9%87%8F%E5%88%87%E7%89%87%E5%9B%BE%E5%B1%82"><span class="toc-number">6.1.</span> <span class="toc-text">矢量切片图层</span></a><ol class="toc-child"><li class="toc-item toc-level-4"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E7%9F%A2%E9%87%8F%E6%95%B0%E6%8D%AE%E6%B8%B2%E6%9F%93%E4%B8%96%E7%95%8C%E5%9C%B0%E5%9B%BE"><span class="toc-number">6.1.1.</span> <span class="toc-text">使用矢量数据渲染世界地图</span></a></li></ol></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8MapBox-Style%E5%AE%9A%E4%B9%89"><span class="toc-number">6.2.</span> <span class="toc-text">使用MapBox Style定义</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%A0%B9%E6%8D%AEMapBox-Style%E5%AE%9A%E4%B9%89%E6%9E%84%E5%BB%BA%E4%B8%80%E5%B9%85%E5%AE%8C%E6%95%B4%E7%9A%84%E5%9C%B0%E5%9B%BE"><span class="toc-number">6.3.</span> <span class="toc-text">根据MapBox Style定义构建一幅完整的地图</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%B8%8E%E7%9F%A2%E9%87%8F%E5%88%87%E7%89%87%E8%A6%81%E7%B4%A0%E4%BA%A4%E4%BA%92"><span class="toc-number">6.4.</span> <span class="toc-text">与矢量切片要素交互</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#WebGL%E7%82%B9%E7%9A%84%E6%B8%B2%E6%9F%93"><span class="toc-number">7.</span> <span class="toc-text">WebGL点的渲染</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8Canvas-2D%E6%B8%B2%E6%9F%93%E7%82%B9"><span class="toc-number">7.1.</span> <span class="toc-text">使用Canvas 2D渲染点</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8WebGL%E6%B8%B2%E6%9F%93%E7%82%B9"><span class="toc-number">7.2.</span> <span class="toc-text">使用WebGL渲染点</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%88%B6%E4%BD%9C%E9%99%A8%E7%9F%B3%E6%92%9E%E5%87%BB%E5%8A%A8%E7%94%BB"><span class="toc-number">7.3.</span> <span class="toc-text">制作陨石撞击动画</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E9%83%A8%E7%BD%B2"><span class="toc-number">8.</span> <span class="toc-text">部署</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E6%95%B0%E6%8D%AE%E5%88%87%E7%89%87"><span class="toc-number">9.</span> <span class="toc-text">数据切片</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%88%9B%E5%BB%BA%E5%9C%B0%E5%9B%BE"><span class="toc-number">9.1.</span> <span class="toc-text">创建地图</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B8%B2%E6%9F%93%E9%AB%98%E7%A8%8B%E6%95%B0%E6%8D%AE"><span class="toc-number">9.2.</span> <span class="toc-text">渲染高程数据</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E6%B8%B2%E6%9F%93%E6%B5%B7%E5%B9%B3%E9%9D%A2"><span class="toc-number">9.3.</span> <span class="toc-text">渲染海平面</span></a></li></ol></li></ol></div></div><div class="card-widget card-recent-post"><div class="item-headline"><i class="fas fa-history"></i><span>最新文章</span></div><div class="aside-list"><div class="aside-list-item"><a class="thumbnail" href="/posts/965064e3/" title="ol/style/Style【译】"><img src= "/img/friend_404.gif" data-lazy-src="https://u-1303826255.cos.ap-nanjing.myqcloud.com/openlayers/202403232115680.png?imageMogr2/quality/75/format/webp/interlace/0" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="ol/style/Style【译】"/></a><div class="content"><a class="title" href="/posts/965064e3/" title="ol/style/Style【译】">ol/style/Style【译】</a><time datetime="2024-03-23T12:42:29.000Z" title="发表于 2024-03-23 20:42:29">2024-03-23</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/b0659e71/" title="OGC矢量切片(地理)【译】"><img src= "/img/friend_404.gif" data-lazy-src="https://u-1303826255.cos.ap-nanjing.myqcloud.com/openlayers/202304212152162.jpg?imageMogr2/quality/75/format/webp/interlace/0" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="OGC矢量切片(地理)【译】"/></a><div class="content"><a class="title" href="/posts/b0659e71/" title="OGC矢量切片(地理)【译】">OGC矢量切片(地理)【译】</a><time datetime="2024-03-10T13:51:44.000Z" title="发表于 2024-03-10 21:51:44">2024-03-10</time></div></div><div class="aside-list-item no-cover"><div class="content"><a class="title" href="/posts/33015ef3/" title="谷歌地图【译】">谷歌地图【译】</a><time datetime="2024-03-10T13:21:31.000Z" title="发表于 2024-03-10 21:21:31">2024-03-10</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/38077f94/" title="悬停提示【译】"><img src= "/img/friend_404.gif" data-lazy-src="https://u-1303826255.cos.ap-nanjing.myqcloud.com/openlayers/202311162246793.jpg?imageMogr2/quality/75/format/webp/interlace/0" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="悬停提示【译】"/></a><div class="content"><a class="title" href="/posts/38077f94/" title="悬停提示【译】">悬停提示【译】</a><time datetime="2023-11-16T13:22:47.000Z" title="发表于 2023-11-16 21:22:47">2023-11-16</time></div></div><div class="aside-list-item"><a class="thumbnail" href="/posts/5c98ce7f/" title="居住地渲染【译】"><img src= "/img/friend_404.gif" data-lazy-src="https://u-1303826255.cos.ap-nanjing.myqcloud.com/openlayers/202311162246139.jpg?imageMogr2/quality/75/format/webp/interlace/0" onerror="this.onerror=null;this.src='/img/404.jpg'" alt="居住地渲染【译】"/></a><div class="content"><a class="title" href="/posts/5c98ce7f/" title="居住地渲染【译】">居住地渲染【译】</a><time datetime="2023-11-16T12:25:46.000Z" title="发表于 2023-11-16 20:25:46">2023-11-16</time></div></div></div></div><div class="card-widget card-categories"><div class="item-headline">
            <i class="fas fa-folder-open"></i>
            <span>分类</span>
            
            </div>
            <ul class="card-category-list" id="aside-cat-list">
            <li class="card-category-list-item "><a class="card-category-list-link" href="/categories/GIS%E8%B5%84%E6%BA%90/"><span class="card-category-list-name">GIS资源</span><span class="card-category-list-count">1</span></a></li><li class="card-category-list-item "><a class="card-category-list-link" href="/categories/Openlayers/"><span class="card-category-list-name">Openlayers</span><span class="card-category-list-count">249</span></a></li>
            </ul></div><div class="card-widget card-tags"><div class="item-headline"><i class="fas fa-tags"></i><span>标签</span></div><div class="card-tag-cloud"><a href="/tags/epsg4326/" style="font-size: 1.17em; color: rgb(178, 199, 185)">epsg4326<sup>2</sup></a><a href="/tags/vectortiles/" style="font-size: 1.22em; color: rgb(29, 33, 82)">vectortiles<sup>5</sup></a><a href="/tags/mapboxstyle/" style="font-size: 1.17em; color: rgb(15, 134, 104)">mapboxstyle<sup>2</sup></a><a href="/tags/ol-mapbox-style/" style="font-size: 1.17em; color: rgb(66, 61, 118)">ol-mapbox-style<sup>2</sup></a><a href="/tags/maptiler/" style="font-size: 1.42em; color: rgb(47, 53, 179)">maptiler<sup>26</sup></a><a href="/tags/grid/" style="font-size: 1.3em; color: rgb(62, 0, 171)">grid<sup>8</sup></a><a href="/tags/vector/" style="font-size: 1.45em; color: rgb(122, 141, 91)">vector<sup>79</sup></a><a href="/tags/esri/" style="font-size: 1.2em; color: rgb(80, 27, 64)">esri<sup>3</sup></a><a href="/tags/ArcGIS/" style="font-size: 1.2em; color: rgb(90, 162, 195)">ArcGIS<sup>3</sup></a><a href="/tags/REST/" style="font-size: 1.2em; color: rgb(5, 80, 21)">REST<sup>3</sup></a><a href="/tags/Feature/" style="font-size: 1.17em; color: rgb(90, 66, 38)">Feature<sup>2</sup></a><a href="/tags/Service/" style="font-size: 1.17em; color: rgb(16, 150, 98)">Service<sup>2</sup></a><a href="/tags/loading/" style="font-size: 1.3em; color: rgb(85, 148, 112)">loading<sup>8</sup></a><a href="/tags/server/" style="font-size: 1.22em; color: rgb(198, 120, 68)">server<sup>5</sup></a><a href="/tags/arcgis/" style="font-size: 1.17em; color: rgb(175, 61, 155)">arcgis<sup>2</sup></a><a href="/tags/image/" style="font-size: 1.27em; color: rgb(65, 15, 82)">image<sup>7</sup></a><a href="/tags/dynamiclayer/" style="font-size: 1.15em; color: rgb(148, 57, 199)">dynamiclayer<sup>1</sup></a><a href="/tags/cog/" style="font-size: 1.35em; color: rgb(189, 169, 52)">cog<sup>13</sup></a><a href="/tags/ndvi/" style="font-size: 1.2em; color: rgb(13, 59, 113)">ndvi<sup>3</sup></a><a href="/tags/ndwi/" style="font-size: 1.15em; color: rgb(117, 111, 93)">ndwi<sup>1</sup></a><a href="/tags/sentinel/" style="font-size: 1.15em; color: rgb(20, 97, 175)">sentinel<sup>1</sup></a><a href="/tags/geotiff/" style="font-size: 1.17em; color: rgb(185, 21, 139)">geotiff<sup>2</sup></a><a href="/tags/projection/" style="font-size: 1.35em; color: rgb(101, 81, 193)">projection<sup>13</sup></a><a href="/tags/proj4js/" style="font-size: 1.25em; color: rgb(2, 186, 14)">proj4js<sup>6</sup></a><a href="/tags/layers/" style="font-size: 1.17em; color: rgb(16, 21, 145)">layers<sup>2</sup></a><a href="/tags/openstreetmap/" style="font-size: 1.4em; color: rgb(195, 147, 183)">openstreetmap<sup>22</sup></a><a href="/tags/canvas/" style="font-size: 1.25em; color: rgb(92, 64, 189)">canvas<sup>6</sup></a><a href="/tags/simple/" style="font-size: 1.22em; color: rgb(31, 140, 103)">simple<sup>5</sup></a><a href="/tags/attribution/" style="font-size: 1.15em; color: rgb(64, 1, 66)">attribution<sup>1</sup></a><a href="/tags/animation/" style="font-size: 1.22em; color: rgb(74, 75, 74)">animation<sup>5</sup></a><a href="/tags/style/" style="font-size: 1.42em; color: rgb(191, 64, 102)">style<sup>26</sup></a><a href="/tags/icon/" style="font-size: 1.32em; color: rgb(183, 155, 162)">icon<sup>9</sup></a><a href="/tags/gif/" style="font-size: 1.15em; color: rgb(68, 124, 131)">gif<sup>1</sup></a><a href="/tags/GPX/" style="font-size: 1.15em; color: rgb(18, 194, 2)">GPX<sup>1</sup></a><a href="/tags/geojson/" style="font-size: 1.32em; color: rgb(56, 78, 58)">geojson<sup>9</sup></a><a href="/tags/tilepyramid/" style="font-size: 1.15em; color: rgb(138, 68, 144)">tilepyramid<sup>1</sup></a><a href="/tags/stac/" style="font-size: 1.17em; color: rgb(88, 193, 62)">stac<sup>2</sup></a><a href="/tags/webgl/" style="font-size: 1.38em; color: rgb(140, 94, 67)">webgl<sup>17</sup></a><a href="/tags/reprojection/" style="font-size: 1.3em; color: rgb(73, 142, 68)">reprojection<sup>8</sup></a><a href="/tags/complex-geometry/" style="font-size: 1.15em; color: rgb(29, 84, 72)">complex-geometry<sup>1</sup></a></div></div><div class="card-widget card-archives"><div class="item-headline"><i class="fas fa-archive"></i><span>时间轴</span></div><ul class="card-archive-list"><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2024/03/"><span class="card-archive-list-date">三月 2024</span><span class="card-archive-list-count">3</span></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/11/"><span class="card-archive-list-date">十一月 2023</span><span class="card-archive-list-count">2</span></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/10/"><span class="card-archive-list-date">十月 2023</span><span class="card-archive-list-count">11</span></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/05/"><span class="card-archive-list-date">五月 2023</span><span class="card-archive-list-count">52</span></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/04/"><span class="card-archive-list-date">四月 2023</span><span class="card-archive-list-count">181</span></a></li><li class="card-archive-list-item"><a class="card-archive-list-link" href="/archives/2023/01/"><span class="card-archive-list-date">一月 2023</span><span class="card-archive-list-count">1</span></a></li></ul></div><div class="card-widget card-webinfo"><div class="item-headline"><i class="fas fa-chart-line"></i><span>网站资讯</span></div><div class="webinfo"><div class="webinfo-item"><div class="item-name">文章数目 :</div><div class="item-count">250</div></div><div class="webinfo-item"><div class="item-name">已运行时间 :</div><div class="item-count" id="runtimeshow" data-publishDate="2022-12-13T16:00:00.000Z"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">本站总字数 :</div><div class="item-count">177.9k</div></div><div class="webinfo-item"><div class="item-name">本站访客数 :</div><div class="item-count" id="busuanzi_value_site_uv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">本站总访问量 :</div><div class="item-count" id="busuanzi_value_site_pv"><i class="fa-solid fa-spinner fa-spin"></i></div></div><div class="webinfo-item"><div class="item-name">最后更新时间 :</div><div class="item-count" id="last-push-date" data-lastPushDate="2024-04-25T00:57:24.420Z"><i class="fa-solid fa-spinner fa-spin"></i></div></div></div></div></div></div></main><footer id="footer" style="background-image: url('/img/homepage-banner.webp')"><div id="footer-wrap"><div class="copyright">&copy;2022 - 2024 <i style="color:red" class="fa fa-heartbeat fa-beat"></i> By lovewhoilove</div><div class="footer_custom_text">生活原本沉闷，但跑起来就有风🎈</div></div></footer></div><div id="rightside"><div id="rightside-config-hide"></div><div id="rightside-config-show"><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><a class="icon-V hidden" onclick="switchNightMode()" title="浅色和深色模式转换"><svg width="25" height="25" viewBox="0 0 1024 1024"><use id="modeicon" xlink:href="#icon-moon"></use></svg></a><a id="to_comment" href="#post-comment" title="直达评论"><i class="fas fa-comments"></i></a><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button><button id="go-up" type="button" title="回到顶部"><span class="scroll-percent"></span><i class="fas fa-arrow-up"></i></button><button id="go-down" type="button" title="直达底部" onclick="btf.scrollToDest(document.body.scrollHeight, 500)"><i class="fas fa-arrow-down"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload/dist/lazyload.iife.min.js"></script><script>function panguFn () {
  if (typeof pangu === 'object') pangu.autoSpacingPage()
  else {
    getScript('https://cdn.jsdelivr.net/npm/pangu/dist/browser/pangu.min.js')
      .then(() => {
        pangu.autoSpacingPage()
      })
  }
}

function panguInit () {
  if (false){
    GLOBAL_CONFIG_SITE.isPost && panguFn()
  } else {
    panguFn()
  }
}

document.addEventListener('DOMContentLoaded', panguInit)</script><script src="/js/search/local-search.js"></script><div class="js-pjax"><script>(() => {
  const $mermaidWrap = document.querySelectorAll('#article-container .mermaid-wrap')
  if ($mermaidWrap.length) {
    window.runMermaid = () => {
      window.loadMermaid = true
      const theme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'default'

      Array.from($mermaidWrap).forEach((item, index) => {
        const mermaidSrc = item.firstElementChild
        const mermaidThemeConfig = '%%{init:{ \'theme\':\'' + theme + '\'}}%%\n'
        const mermaidID = 'mermaid-' + index
        const mermaidDefinition = mermaidThemeConfig + mermaidSrc.textContent
        mermaid.mermaidAPI.render(mermaidID, mermaidDefinition, (svgCode) => {
          mermaidSrc.insertAdjacentHTML('afterend', svgCode)
        })
      })
    }

    const loadMermaid = () => {
      window.loadMermaid ? runMermaid() : getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(runMermaid)
    }

    window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid)
  }
})()</script><script>function loadValine () {
  function initValine () {
    const valine = new Valine(Object.assign({
      el: '#vcomment',
      appId: 's2D3RXeuiTBrJjXpdIo4bJuK-gzGzoHsz',
      appKey: 'e6j6L6lA50bj18TlkcZEMw0O',
      avatar: 'monsterid',
      serverURLs: '',
      emojiMaps: "",
      path: window.location.pathname,
      visitor: false
    }, {"placeholder":"畅所欲言哈😁~~"}))
  }

  if (typeof Valine === 'function') initValine() 
  else getScript('https://cdn.jsdelivr.net/npm/valine/dist/Valine.min.js').then(initValine)
}

if ('Valine' === 'Valine' || !true) {
  if (true) btf.loadComment(document.getElementById('vcomment'),loadValine)
  else setTimeout(loadValine, 0)
} else {
  function loadOtherComment () {
    loadValine()
  }
}</script></div><script src="/js/sun_moon.js"></script><script src="/js/jquery.min.js"></script><script data-pjax defer src="/js/chocolate.js"></script><script defer type="text/javascript" src="/js/sweetalert2.all.js"></script><script defer src="/js/lunar.js"></script><script defer src="/js/day.js"></script><script data-pjax defer src="/js/fishes.js"></script><canvas id="universe"></canvas><script src="/js/universe.js"></script><script defer="defer" id="fluttering_ribbon" mobile="false" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/canvas-fluttering-ribbon.min.js"></script><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/activate-power-mode.min.js"></script><script>POWERMODE.colorful = true;
POWERMODE.shake = true;
POWERMODE.mobile = false;
document.body.addEventListener('input', POWERMODE);
</script><script id="click-heart" src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/dist/click-heart.min.js" async="async" mobile="false"></script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/metingjs/dist/Meting.min.js"></script><script src="https://cdn.jsdelivr.net/npm/pjax/pjax.min.js"></script><script>let pjaxSelectors = ["head > title","#config-diff","#body-wrap","#rightside-config-hide","#rightside-config-show",".js-pjax"]

var pjax = new Pjax({
  elements: 'a:not([target="_blank"])',
  selectors: pjaxSelectors,
  cacheBust: false,
  analytics: false,
  scrollRestoration: false
})

document.addEventListener('pjax:send', function () {

  // removeEventListener scroll 
  window.tocScrollFn && window.removeEventListener('scroll', window.tocScrollFn)
  window.scrollCollect && window.removeEventListener('scroll', scrollCollect)

  document.getElementById('rightside').style.cssText = "opacity: ''; transform: ''"
  
  if (window.aplayers) {
    for (let i = 0; i < window.aplayers.length; i++) {
      if (!window.aplayers[i].options.fixed) {
        window.aplayers[i].destroy()
      }
    }
  }

  typeof typed === 'object' && typed.destroy()

  //reset readmode
  const $bodyClassList = document.body.classList
  $bodyClassList.contains('read-mode') && $bodyClassList.remove('read-mode')

  typeof disqusjs === 'object' && disqusjs.destroy()
})

document.addEventListener('pjax:complete', function () {
  window.refreshFn()

  document.querySelectorAll('script[data-pjax]').forEach(item => {
    const newScript = document.createElement('script')
    const content = item.text || item.textContent || item.innerHTML || ""
    Array.from(item.attributes).forEach(attr => newScript.setAttribute(attr.name, attr.value))
    newScript.appendChild(document.createTextNode(content))
    item.parentNode.replaceChild(newScript, item)
  })

  GLOBAL_CONFIG.islazyload && window.lazyLoadInstance.update()

  typeof chatBtnFn === 'function' && chatBtnFn()
  typeof panguInit === 'function' && panguInit()

  // google analytics
  typeof gtag === 'function' && gtag('config', '', {'page_path': window.location.pathname});

  // baidu analytics
  typeof _hmt === 'object' && _hmt.push(['_trackPageview',window.location.pathname]);

  typeof loadMeting === 'function' && document.getElementsByClassName('aplayer').length && loadMeting()

  // prismjs
  typeof Prism === 'object' && Prism.highlightAll()
})

document.addEventListener('pjax:error', (e) => {
  if (e.request.status === 404) {
    pjax.loadUrl('/404.html')
  }
})</script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div><!-- hexo injector body_end start --><div class="js-pjax"><script async="async">var arr = document.getElementsByClassName('recent-post-item');
for(var i = 0;i<arr.length;i++){
    arr[i].classList.add('wow');
    arr[i].classList.add('animate__zoomIn');
    arr[i].setAttribute('data-wow-duration', '2s');
    arr[i].setAttribute('data-wow-delay', '1s');
    arr[i].setAttribute('data-wow-offset', '100');
    arr[i].setAttribute('data-wow-iteration', '1');
  }</script><script async="async">var arr = document.getElementsByClassName('card-widget');
for(var i = 0;i<arr.length;i++){
    arr[i].classList.add('wow');
    arr[i].classList.add('animate__zoomIn');
    arr[i].setAttribute('data-wow-duration', '');
    arr[i].setAttribute('data-wow-delay', '');
    arr[i].setAttribute('data-wow-offset', '');
    arr[i].setAttribute('data-wow-iteration', '');
  }</script></div><script defer src="https://npm.elemecdn.com/hexo-butterfly-wowjs/lib/wow.min.js"></script><script defer src="https://npm.elemecdn.com/hexo-butterfly-wowjs/lib/wow_init.js"></script><!-- hexo injector body_end end --></body></html>